diff --git a/README.md b/README.md index 544337be..4c9690c3 100644 --- a/README.md +++ b/README.md @@ -93,8 +93,9 @@ Hint: Look at Extra Tips (at the bottom of README) for a more detailed guide on ``` -[//]: # (has_booked_call ) [//]: # (has_signed_up ) +support_call_count: Numerical +sales_call_count: Numerical tc2_status: Large text tc2_cligency_url: Large text hermes_id: Numerical diff --git a/app/admin/resources.py b/app/admin/resources.py index b245c704..689c1493 100644 --- a/app/admin/resources.py +++ b/app/admin/resources.py @@ -141,7 +141,8 @@ class CompanyResource(Model): 'paid_invoice_count', 'estimated_income', 'currency', - 'has_booked_call', + 'sales_call_count', + 'support_call_count', 'has_signed_up', 'utm_campaign', 'utm_source', diff --git a/app/callbooker/_process.py b/app/callbooker/_process.py index 1a0c228d..7415663b 100644 --- a/app/callbooker/_process.py +++ b/app/callbooker/_process.py @@ -93,9 +93,6 @@ async def get_or_create_contact_company(event: CBSalesCall) -> tuple[Company, Co await company.save() contact = contact or await get_or_create_contact(company, event) app_logger.info(f'Got company {company} and contact {contact} from {event}') - - company.has_booked_call = True - await company.save() return company, contact diff --git a/app/callbooker/views.py b/app/callbooker/views.py index 7207ee28..60131e5c 100644 --- a/app/callbooker/views.py +++ b/app/callbooker/views.py @@ -40,6 +40,8 @@ async def sales_call(event: CBSalesCall, tasks: BackgroundTasks): else: meeting.deal = deal await meeting.save() + company.sales_call_count += 1 + await company.save() tasks.add_task(pd_post_process_sales_call, company=company, contact=contact, deal=deal, meeting=meeting) return {'status': 'ok'} @@ -59,6 +61,8 @@ async def support_call(event: CBSupportCall, tasks: BackgroundTasks): return JSONResponse({'status': 'error', 'message': str(e)}, status_code=400) else: await meeting.save() + company.support_call_count += 1 + await company.save() tasks.add_task(pd_post_process_support_call, contact=contact, meeting=meeting) return {'status': 'ok'} diff --git a/app/models.py b/app/models.py index a40a70df..68930d63 100644 --- a/app/models.py +++ b/app/models.py @@ -236,7 +236,8 @@ class Company(HermesModel): paid_invoice_count = fields.IntField(default=0) estimated_income = fields.CharField(max_length=255, null=True) currency = fields.CharField(max_length=255, null=True) - has_booked_call = fields.BooleanField(default=False) + sales_call_count = fields.IntField(default=0) + support_call_count = fields.IntField(default=0) has_signed_up = fields.BooleanField(default=False) utm_campaign = fields.CharField(max_length=255, null=True) utm_source = fields.CharField(max_length=255, null=True) diff --git a/app/pipedrive/tasks.py b/app/pipedrive/tasks.py index af7be682..243ea119 100644 --- a/app/pipedrive/tasks.py +++ b/app/pipedrive/tasks.py @@ -31,10 +31,13 @@ async def pd_post_process_sales_call(company: Company, contact: Contact, meeting async def pd_post_process_support_call(contact: Contact, meeting: Meeting): """ - Called after a support call is booked. Creates the activity if the contact have a pipedrive id + Called after a support call is booked. Creates/updates the Org & Person in pipedrive, + Creates the activity if the contact have a pipedrive id """ with logfire.span('pd_post_process_support_call'): - if (await contact.company).pd_org_id: + company = await contact.company + if company and company.pd_org_id: + await get_and_create_or_update_organisation(company) await get_and_create_or_update_person(contact) await create_activity(meeting) diff --git a/migrations/models/8_20240913013916_update.py b/migrations/models/8_20240913013916_update.py new file mode 100644 index 00000000..7ef6b17d --- /dev/null +++ b/migrations/models/8_20240913013916_update.py @@ -0,0 +1,15 @@ +from tortoise import BaseDBAsyncClient + + +async def upgrade(db: BaseDBAsyncClient) -> str: + return """ + ALTER TABLE "company" ADD "support_call_count" INT NOT NULL DEFAULT 0; + ALTER TABLE "company" ADD "sales_call_count" INT NOT NULL DEFAULT 0; + ALTER TABLE "company" DROP COLUMN "has_booked_call";""" + + +async def downgrade(db: BaseDBAsyncClient) -> str: + return """ + ALTER TABLE "company" ADD "has_booked_call" BOOL NOT NULL DEFAULT False; + ALTER TABLE "company" DROP COLUMN "support_call_count"; + ALTER TABLE "company" DROP COLUMN "sales_call_count";""" diff --git a/patch.py b/patch.py index 0077c40b..772eaefe 100644 --- a/patch.py +++ b/patch.py @@ -15,7 +15,7 @@ from app.tc2.tasks import update_client_from_company from tortoise.expressions import Q from tortoise import Tortoise -from app.models import Company +from app.models import Company, Meeting import logfire async def init(): @@ -91,6 +91,35 @@ async def update_pd_org_price_plans(): print(f'Updated {companies_updated} companies') +@command +async def update_call_counts(): + """ + This patch updates the call counts for companies, by searching though all meetings + """ + meetings = await Meeting.all() + print(f'Updating call counts from {len(meetings)} meetings') + for meeting in meetings: + deal = await meeting.deal + if not deal: + continue + company = await deal.company + if company: + if meeting.meeting_type == Meeting.TYPE_SALES: + company.sales_call_count += 1 + print(f'Incremented company {company.id} with {company.sales_call_count} sales call count') + elif meeting.meeting_type == Meeting.TYPE_SUPPORT: + company.support_call_count += 1 + print(f'Incremented company {company.id} with {company.support_call_count} support call count') + await company.save() + + # send a task to update the Organisation in Pipedrive + print(f'Sending task to update company {company.id} in Pipedrive') + await get_and_create_or_update_organisation(company) + + + + + # End of patch commands @click.command() diff --git a/scripts/inital_db.py b/scripts/inital_db.py deleted file mode 100644 index ab60ec5f..00000000 --- a/scripts/inital_db.py +++ /dev/null @@ -1,611 +0,0 @@ -from tortoise import Tortoise, run_async - -from app.models import Admin, Company, Config, Contact, CustomField, Deal, Pipeline, Stage -from app.utils import logger - - -async def setup_database(): - """ - This function is called from scripts/initial_db.py and scripts/initial_db_test.py to set up the database when - testing locally, to save wasting 40 mins setting this up each time I have to reset-db. - """ - - # Currently you to need to run uvicorn, login, close the server, then run this script. - - logger.info('Creating Admins') - # Create the first Admin instance - await Admin.update_or_create( - id=100, - defaults={ - 'username': 'testing@tutorcruncher.com', - 'password': None, - 'tc2_admin_id': 0, - 'pd_owner_id': 0, - 'first_name': 'Testing', - 'last_name': 'Testing', - 'timezone': 'Europe/London', - 'is_sales_person': False, - 'is_support_person': False, - 'is_bdr_person': False, - 'sells_payg': False, - 'sells_startup': False, - 'sells_enterprise': False, - }, - ) - - await Admin.update_or_create( - id=2, - defaults={ - 'username': 'fionn@tutorcruncher.com', - 'password': 'testing', - 'tc2_admin_id': 68, - 'pd_owner_id': 16532867, - 'first_name': 'Fionn', - 'last_name': 'Finegan', - 'timezone': 'Europe/London', - 'is_sales_person': True, - 'is_support_person': False, - 'is_bdr_person': False, - 'sells_payg': False, - 'sells_startup': False, - 'sells_enterprise': True, - }, - ) - - # Create the second Admin instance - await Admin.update_or_create( - id=1, - defaults={ - 'username': 'sam@tutorcruncher.com', - 'password': 'testing', - 'tc2_admin_id': 67, - 'pd_owner_id': 16532834, - 'first_name': 'Sam', - 'last_name': 'Linge', - 'timezone': 'Europe/London', - 'is_sales_person': True, - 'is_support_person': False, - 'is_bdr_person': False, - 'sells_payg': True, - 'sells_startup': True, - 'sells_enterprise': False, - }, - ) - - # Create the third Admin instance - await Admin.update_or_create( - id=3, - defaults={ - 'username': 'raashi@tutorcurnhcer.com', - 'password': 'testing', - 'tc2_admin_id': 69, - 'pd_owner_id': 0, - 'first_name': 'Raashi', - 'last_name': 'Thakran', - 'timezone': 'Europe/London', - 'is_sales_person': False, - 'is_support_person': True, - 'is_bdr_person': False, - 'sells_payg': False, - 'sells_startup': False, - 'sells_enterprise': False, - }, - ) - - # Create the fourth Admin instance - await Admin.update_or_create( - id=4, - defaults={ - 'username': 'maahi@tutorcruncher.com', - 'password': 'testing', - 'tc2_admin_id': 70, - 'pd_owner_id': 0, - 'first_name': 'Maahi', - 'last_name': 'Islam', - 'timezone': 'Europe/London', - 'is_sales_person': False, - 'is_support_person': True, - 'is_bdr_person': False, - 'sells_payg': False, - 'sells_startup': False, - 'sells_enterprise': False, - }, - ) - - # Create the fifth Admin instance - await Admin.update_or_create( - id=7, - defaults={ - 'username': 'gabe@tutorcruncher.com', - 'password': 'testing', - 'tc2_admin_id': 71, - 'pd_owner_id': 16532779, - 'first_name': 'Gabe', - 'last_name': 'Gatrill', - 'timezone': 'Europe/London', - 'is_sales_person': False, - 'is_support_person': False, - 'is_bdr_person': True, - 'sells_payg': False, - 'sells_startup': False, - 'sells_enterprise': False, - }, - ) - - # Assuming Company is a class with a create method - logger.info('Creating Companies') - # Create the first Company instance - await Company.update_or_create( - id=168, - defaults={ - 'name': 'rake dog', - 'tc2_agency_id': 23, - 'tc2_cligency_id': 1351, - 'tc2_status': 'pending_email_conf', - 'pd_org_id': 362, - 'created': '2023-11-24 09:04:59.3112+00', - 'price_plan': 'payg', - 'country': 'GB', - 'website': 'http://test.test.com', - 'paid_invoice_count': 0, - 'estimated_income': '£10,000 - £20,000', - 'currency': None, - 'has_booked_call': False, - 'has_signed_up': False, - 'utm_campaign': None, - 'utm_source': None, - 'narc': False, - 'bdr_person_id': 1, - 'sales_person_id': 3, - 'support_person_id': None, - }, - ) - - # Create the second Company instance - await Company.update_or_create( - id=169, - defaults={ - 'name': 'xenogenesis yearn spoon', - 'tc2_agency_id': 24, - 'tc2_cligency_id': 1412, - 'tc2_status': 'pending_email_conf', - 'pd_org_id': 363, - 'created': '2023-11-24 09:19:08.10572+00', - 'price_plan': 'startup', - 'country': 'GB', - 'website': 'http://super.trooper.com', - 'paid_invoice_count': 0, - 'estimated_income': '£50,000 - £100,000', - 'currency': None, - 'has_booked_call': False, - 'has_signed_up': False, - 'utm_campaign': None, - 'utm_source': None, - 'narc': False, - 'bdr_person_id': 1, - 'sales_person_id': 4, - 'support_person_id': None, - }, - ) - - # Create the third Company instance - await Company.update_or_create( - id=170, - defaults={ - 'name': 'brave meraki', - 'tc2_agency_id': 25, - 'tc2_cligency_id': 1473, - 'tc2_status': 'pending_email_conf', - 'pd_org_id': 364, - 'created': '2023-11-24 09:27:36.750212+00', - 'price_plan': 'payg', - 'country': 'GB', - 'website': 'http://test.test.com', - 'paid_invoice_count': 0, - 'estimated_income': '£50,000 - £100,000', - 'currency': None, - 'has_booked_call': False, - 'has_signed_up': False, - 'utm_campaign': None, - 'utm_source': None, - 'narc': False, - 'bdr_person_id': 1, - 'sales_person_id': 3, - 'support_person_id': None, - }, - ) - - # Create the fourth Company instance - await Company.update_or_create( - id=166, - defaults={ - 'name': '123456', - 'tc2_agency_id': 21, - 'tc2_cligency_id': 1229, - 'tc2_status': 'pending_email_conf', - 'pd_org_id': 360, - 'created': '2023-11-23 17:25:53.824357+00', - 'price_plan': 'payg', - 'country': 'GB', - 'website': 'http://www.onetwothree.com', - 'paid_invoice_count': 0, - 'estimated_income': 'just starting out', - 'currency': None, - 'has_booked_call': False, - 'has_signed_up': False, - 'utm_campaign': None, - 'utm_source': None, - 'narc': False, - 'bdr_person_id': 1, - 'sales_person_id': 3, - 'support_person_id': None, - }, - ) - - # Create the fifth Company instance - await Company.update_or_create( - id=167, - defaults={ - 'name': 'infinite fork', - 'tc2_agency_id': 22, - 'tc2_cligency_id': 1290, - 'tc2_status': 'pending_email_conf', - 'pd_org_id': 361, - 'created': '2023-11-23 17:30:59.228017+00', - 'price_plan': 'payg', - 'country': 'GB', - 'website': 'http://test.test.com', - 'paid_invoice_count': 0, - 'estimated_income': 'just starting out', - 'currency': None, - 'has_booked_call': False, - 'has_signed_up': False, - 'utm_campaign': None, - 'utm_source': None, - 'narc': False, - 'bdr_person_id': 1, - 'sales_person_id': 4, - 'support_person_id': None, - }, - ) - - logger.info('Creating Contact') - # Create instances for the "public.contact" table - await Contact.update_or_create( - id=125, - defaults={ - 'tc2_sr_id': 1291, - 'pd_person_id': 173, - 'created': '2023-11-23 17:30:59.2316+00', - 'first_name': 'opulent', - 'last_name': 'yarn', - 'email': None, - 'phone': None, - 'country': None, - 'company_id': 167, - }, - ) - - await Contact.update_or_create( - id=126, - defaults={ - 'tc2_sr_id': 1352, - 'pd_person_id': 174, - 'created': '2023-11-24 09:04:59.320119+00', - 'first_name': 'optimistic', - 'last_name': 'cherry', - 'email': 'rainy@abrasive.com', - 'phone': None, - 'country': None, - 'company_id': 168, - }, - ) - - await Contact.update_or_create( - id=127, - defaults={ - 'tc2_sr_id': 1413, - 'pd_person_id': 175, - 'created': '2023-11-24 09:19:08.116621+00', - 'first_name': 'youthful', - 'last_name': 'pencil', - 'email': 'lock@gestalt.com', - 'phone': None, - 'country': None, - 'company_id': 169, - }, - ) - - await Contact.update_or_create( - id=128, - defaults={ - 'tc2_sr_id': 1474, - 'pd_person_id': 176, - 'created': '2023-11-24 09:27:36.759544+00', - 'first_name': 'inquisitive', - 'last_name': 'genteel television', - 'email': 'blue@small.com', - 'phone': None, - 'country': None, - 'company_id': 170, - }, - ) - - await Contact.update_or_create( - id=124, - defaults={ - 'tc2_sr_id': 1230, - 'pd_person_id': 172, - 'created': '2023-11-23 17:25:53.828285+00', - 'first_name': '123456', - 'last_name': '123456', - 'email': None, - 'phone': None, - 'country': None, - 'company_id': 166, - }, - ) - - logger.info('Creating CustomField') - # Create instances for the "public.customfield" table - await CustomField.update_or_create( - id=1, - defaults={ - 'name': 'Website', - 'machine_name': 'website', - 'field_type': 'str', - 'hermes_field_name': 'website', - 'tc2_machine_name': None, - 'pd_field_id': '49206a74cce41f79dcb7944f2de6e0ee42a55b02', - 'linked_object_type': 'Company', - }, - ) - - await CustomField.update_or_create( - id=2, - defaults={ - 'name': 'Paid Invoice Count', - 'machine_name': 'paid_invoice_count', - 'field_type': 'int', - 'hermes_field_name': 'paid_invoice_count', - 'tc2_machine_name': None, - 'pd_field_id': 'e2e7987002fcc6b2f0b6b1dde78e295f79c4e0b7', - 'linked_object_type': 'Company', - }, - ) - - await CustomField.update_or_create( - id=3, - defaults={ - 'name': 'TC2 Cligency URL', - 'machine_name': 'tc2_cligency_url', - 'field_type': 'str', - 'hermes_field_name': 'tc2_cligency_url', - 'tc2_machine_name': None, - 'pd_field_id': 'dd55f5ecb2a95f89d30309ba67ef08ddd5aaa5bb', - 'linked_object_type': 'Company', - }, - ) - - await CustomField.update_or_create( - id=4, - defaults={ - 'name': 'TC2 Status', - 'machine_name': 'tc2_status', - 'field_type': 'str', - 'hermes_field_name': 'tc2_status', - 'tc2_machine_name': None, - 'pd_field_id': '174c4a837a4eeeda6ea9850506964a7db2488229', - 'linked_object_type': 'Company', - }, - ) - - await CustomField.update_or_create( - id=5, - defaults={ - 'name': 'UTM Source', - 'machine_name': 'utm_source', - 'field_type': 'str', - 'hermes_field_name': 'utm_source', - 'tc2_machine_name': None, - 'pd_field_id': '2fa66e880dfbabed928e0fcedb4b027f8802c8af', - 'linked_object_type': 'Company', - }, - ) - - await CustomField.update_or_create( - id=6, - defaults={ - 'name': 'UTM Campaign', - 'machine_name': 'utm_campaign', - 'field_type': 'str', - 'hermes_field_name': 'utm_campaign', - 'tc2_machine_name': None, - 'pd_field_id': 'ee62ff350d2dd1c189d2ea023ee1823a1b94b3a1', - 'linked_object_type': 'Company', - }, - ) - - await CustomField.update_or_create( - id=7, - defaults={ - 'name': 'Estimated Monthly Income', - 'machine_name': 'estimated_monthly_income', - 'field_type': 'str', - 'hermes_field_name': 'estimated_income', - 'tc2_machine_name': 'estimated_monthly_income', - 'pd_field_id': '2f821a5168fa642991fc5ddd3e5e49124f04ebed', - 'linked_object_type': 'Company', - }, - ) - - await CustomField.update_or_create( - id=8, - defaults={ - 'name': 'BDR Person ID', - 'machine_name': 'bdr_person_id', - 'field_type': 'str', - 'hermes_field_name': 'bdr_person_id', - 'tc2_machine_name': 'None', - 'pd_field_id': 'd98f937eed5df4341711ba53052d257a20d47bec', - 'linked_object_type': 'Company', - }, - ) - - await CustomField.update_or_create( - id=9, - defaults={ - 'name': 'Hermes ID', - 'machine_name': 'hermes_id', - 'field_type': 'fk_field', - 'hermes_field_name': 'id', - 'tc2_machine_name': None, - 'pd_field_id': 'ac78ff43095ad2be524b3e0533c2e3f7df91e141', - 'linked_object_type': 'Company', - }, - ) - - await CustomField.update_or_create( - id=10, - defaults={ - 'name': 'Hermes ID', - 'machine_name': 'hermes_id_2', - 'field_type': 'fk_field', - 'hermes_field_name': 'id', - 'tc2_machine_name': None, - 'pd_field_id': '4ede82add98b0baf02f8881e88fa2c6394be0ba8', - 'linked_object_type': 'Contact', - }, - ) - - await CustomField.update_or_create( - id=11, - defaults={ - 'name': 'Hermes ID', - 'machine_name': 'hermes_id_3', - 'field_type': 'fk_field', - 'hermes_field_name': 'id', - 'tc2_machine_name': None, - 'pd_field_id': 'f9fb48b365d26a4b88cd3953aa5b91cd574efd87', - 'linked_object_type': 'Deal', - }, - ) - - logger.info('Creating Stages') - await Stage.update_or_create(id=1, defaults={'pd_stage_id': 7, 'name': 'PAYG Qualified'}) - await Stage.update_or_create(id=2, defaults={'pd_stage_id': 9, 'name': 'PAYG Demo Scheduled'}) - await Stage.update_or_create(id=3, defaults={'pd_stage_id': 11, 'name': 'PAYG Negotiations Started'}) - await Stage.update_or_create(id=4, defaults={'pd_stage_id': 10, 'name': 'PAYG Proposal Made'}) - await Stage.update_or_create(id=9, defaults={'pd_stage_id': 25, 'name': 'STARTUP Qualified'}) - await Stage.update_or_create(id=5, defaults={'pd_stage_id': 27, 'name': 'STARTUP Demo Scheduled'}) - await Stage.update_or_create(id=6, defaults={'pd_stage_id': 26, 'name': 'STARTUP Contact Made'}) - await Stage.update_or_create(id=7, defaults={'pd_stage_id': 29, 'name': 'STARTUP Negotiations Started'}) - await Stage.update_or_create(id=8, defaults={'pd_stage_id': 28, 'name': 'STARTUP Proposal Made'}) - await Stage.update_or_create(id=12, defaults={'pd_stage_id': 31, 'name': 'ENTERPRISE Contact Made'}) - await Stage.update_or_create(id=11, defaults={'pd_stage_id': 32, 'name': 'ENTERPRISE Demo Scheduled'}) - await Stage.update_or_create(id=10, defaults={'pd_stage_id': 30, 'name': 'ENTERPRISE Qualified'}) - await Stage.update_or_create(id=13, defaults={'pd_stage_id': 33, 'name': 'ENTERPRISE Proposal Made'}) - await Stage.update_or_create(id=14, defaults={'pd_stage_id': 34, 'name': 'ENTERPRISE Negotiations Started'}) - - logger.info('Creating Pipelines') - # Updating or Creating records in the Pipeline table - await Pipeline.update_or_create(id=1, defaults={'pd_pipeline_id': 2, 'name': 'PAYG', 'dft_entry_stage_id': 1}) - await Pipeline.update_or_create( - id=3, defaults={'pd_pipeline_id': 6, 'name': 'ENTERPRISE', 'dft_entry_stage_id': 10} - ) - await Pipeline.update_or_create(id=2, defaults={'pd_pipeline_id': 5, 'name': 'STARTUP', 'dft_entry_stage_id': 9}) - - logger.info('Creating Deals') - # Updating or Creating records in the Deal table - await Deal.update_or_create( - id=160, - defaults={ - 'pd_deal_id': 225, - 'name': '123456', - 'status': 'open', - 'admin_id': 1, - 'company_id': 166, - 'contact_id': 124, - 'pipeline_id': 1, - 'stage_id': 1, - }, - ) - await Deal.update_or_create( - id=161, - defaults={ - 'pd_deal_id': 227, - 'name': 'infinite fork', - 'status': 'open', - 'admin_id': 1, - 'company_id': 167, - 'contact_id': 125, - 'pipeline_id': 1, - 'stage_id': 1, - }, - ) - await Deal.update_or_create( - id=162, - defaults={ - 'pd_deal_id': 229, - 'name': 'rake dog', - 'status': 'open', - 'admin_id': 1, - 'company_id': 168, - 'contact_id': 126, - 'pipeline_id': 1, - 'stage_id': 1, - }, - ) - await Deal.update_or_create( - id=163, - defaults={ - 'pd_deal_id': 230, - 'name': 'xenogenesis yearn spoon', - 'status': 'open', - 'admin_id': 1, - 'company_id': 169, - 'contact_id': 127, - 'pipeline_id': 2, - 'stage_id': 9, - }, - ) - await Deal.update_or_create( - id=164, - defaults={ - 'pd_deal_id': 231, - 'name': 'brave meraki', - 'status': 'open', - 'admin_id': 1, - 'company_id': 170, - 'contact_id': 128, - 'pipeline_id': 1, - 'stage_id': 1, - }, - ) - - # Assuming the relevant classes for each table (e.g., Config, Contact, CustomField, CustomFieldValue, Deal, HermesModel, Meeting, Pipeline, Stage) have create methods. - logger.info('Creating Config') - # Create instances for the "public.config" table - await Config.update_or_create( - id=1, - defaults={ - 'meeting_dur_mins': 30, - 'meeting_buffer_mins': 15, - 'meeting_min_start': '10:00', - 'meeting_max_end': '17:30', - 'enterprise_pipeline_id': 3, - 'payg_pipeline_id': 1, - 'startup_pipeline_id': 2, - }, - ) - - -async def run(): - # Initialize Tortoise - await Tortoise.init(db_url='postgres://postgres@localhost:5432/hermes', modules={'models': ['app.models']}) - # Run your function - await setup_database() - - -# Run the function with Tortoise ORM -run_async(run()) diff --git a/tests/test_callbooker.py b/tests/test_callbooker.py index fcb881ad..4f0afb76 100644 --- a/tests/test_callbooker.py +++ b/tests/test_callbooker.py @@ -161,7 +161,7 @@ async def test_com_cli_create_update_1(self, mock_gcal_builder, mock_add_task): assert company.estimated_income == '1000' assert not company.support_person assert not company.bdr_person - assert company.has_booked_call + assert company.sales_call_count == 1 assert await company.sales_person == sales_person contact = await Contact.get() @@ -206,7 +206,7 @@ async def test_com_cli_create_update_2(self, mock_gcal_builder, mock_add_task): assert company.website == 'https://junes.com' assert company.country == 'GB' assert not company.support_person - assert company.has_booked_call + assert company.sales_call_count == 1 assert await company.sales_person == sales_person assert not company.bdr_person @@ -255,7 +255,7 @@ async def test_com_cli_create_update_3(self, mock_gcal_builder, mock_add_task): assert company.country == 'GB' assert not company.support_person assert not company.bdr_person - assert company.has_booked_call + assert company.sales_call_count == 1 assert await company.sales_person == sales_person contact = await Contact.get() @@ -304,7 +304,7 @@ async def test_com_cli_create_update_4(self, mock_gcal_builder, mock_add_task): assert company.country == 'GB' assert not company.support_person assert not company.bdr_person - assert company.has_booked_call + assert company.sales_call_count == 1 assert await company.sales_person == sales_person contact = await Contact.get() @@ -351,7 +351,7 @@ async def test_com_cli_create_update_5(self, mock_gcal_builder, mock_add_task): assert company.country == 'GB' assert not company.support_person assert not company.bdr_person - assert company.has_booked_call + assert company.sales_call_count == 1 contact = await Contact.get() assert contact.first_name == 'B' assert contact.last_name == 'Junes' @@ -398,7 +398,7 @@ async def test_com_cli_create_update_6(self, mock_gcal_builder, mock_add_task): assert company.country == 'GB' assert not company.support_person assert not company.bdr_person - assert company.has_booked_call + assert company.sales_call_count == 1 contact = await Contact.get() assert contact.first_name == 'B' assert contact.last_name == 'J' @@ -437,7 +437,7 @@ async def test_com_cli_create_update_7(self, mock_gcal_builder, mock_add_task): company = await Company.get() assert not company.tc2_cligency_id assert company.name == 'Junes Ltd' - assert company.has_booked_call + assert company.sales_call_count == 1 assert (await company.bdr_person) == bdr_person assert (await company.sales_person) == sales_person @@ -500,7 +500,7 @@ async def test_error_creating_gcal_event(self, mock_gcal_builder, mock_add_task) assert company.country == 'GB' assert not company.support_person assert not company.bdr_person - assert company.has_booked_call + assert company.sales_call_count == 1 contact = await Contact.get() assert contact.first_name == 'B' assert contact.last_name == 'J' @@ -514,6 +514,64 @@ async def test_error_creating_gcal_event(self, mock_gcal_builder, mock_add_task) assert await meeting.contact == contact assert meeting.meeting_type == Meeting.TYPE_SALES + @mock.patch('fastapi.BackgroundTasks.add_task') + @mock.patch('app.callbooker._google.AdminGoogleCalendar._create_resource') + async def test_multiple_sales_calls(self, mock_gcal_builder, mock_add_task): + """ + Book a new meeting + Company doesn't exist so create + Contact doesn't exist so create + Create with admin + + Book a new meeting ... again + """ + mock_gcal_builder.side_effect = fake_gcal_builder() + sales_person = await Admin.create( + first_name='Steve', last_name='Jobs', username='climan@example.com', is_support_person=True + ) + assert await Company.all().count() == 0 + assert await Contact.all().count() == 0 + r = await self.client.post(self.url, json={'admin_id': sales_person.id, **CB_MEETING_DATA}) + assert r.status_code == 200, r.json() + + company = await Company.get() + assert not company.tc2_cligency_id + assert company.name == 'Junes Ltd' + assert company.website == 'https://junes.com' + assert company.country == 'GB' + assert company.estimated_income == '1000' + assert not company.support_person + assert not company.bdr_person + assert company.sales_call_count == 1 + assert await company.sales_person == sales_person + + contact = await Contact.get() + assert contact.first_name == 'Brain' + assert contact.last_name == 'Junes' + assert contact.email == 'brain@junes.com' + assert contact.company_id == company.id + + meeting = await Meeting.get() + assert meeting.status == Meeting.STATUS_PLANNED + assert meeting.start_time == datetime(2026, 7, 3, 9, tzinfo=utc) + assert await meeting.admin == sales_person + assert await meeting.contact == contact + assert meeting.meeting_type == Meeting.TYPE_SALES + + meeting_data = CB_MEETING_DATA.copy() + meeting_data.update(meeting_dt=int(datetime(2026, 8, 3, 11, tzinfo=utc).timestamp()), admin_id=sales_person.id) + + r = await self.client.post(self.url, json=meeting_data) + assert r.status_code == 200, r.json() + + company = await Company.get() + assert not company.tc2_cligency_id + assert company.name == 'Junes Ltd' + assert company.sales_call_count == 2 + + assert await Meeting.all().count() == 2 + assert await Contact.all().count() == 1 + @mock.patch('fastapi.BackgroundTasks.add_task') @mock.patch('app.callbooker._google.AdminGoogleCalendar._create_resource') async def test_admin_busy_start(self, mock_gcal_builder, mock_add_task): @@ -579,6 +637,8 @@ async def test_support_call_book_create_contact(self, mock_gcal_builder, mock_ad Company exists Contact doesn't exist so create Create with client manager + + book another meeting """ mock_gcal_builder.side_effect = fake_gcal_builder() meeting_data = CB_MEETING_DATA.copy() @@ -593,6 +653,7 @@ async def test_support_call_book_create_contact(self, mock_gcal_builder, mock_ad company = await Company.get() assert company.name == 'Julies Ltd' + assert company.support_call_count == 1 contact = await Contact.get() assert contact.first_name == 'Brain' @@ -607,6 +668,17 @@ async def test_support_call_book_create_contact(self, mock_gcal_builder, mock_ad assert await meeting.contact == contact assert meeting.meeting_type == Meeting.TYPE_SUPPORT + meeting_data.update(meeting_dt=int(datetime(2026, 8, 3, 11, tzinfo=utc).timestamp())) + r = await self.client.post('/callbooker/support/book/', json=meeting_data) + assert r.status_code == 200, r.json() + + company = await Company.get() + assert company.name == 'Julies Ltd' + assert company.support_call_count == 2 + + assert await Meeting.all().count() == 2 + assert await Contact.all().count() == 1 + @mock.patch('fastapi.BackgroundTasks.add_task') @mock.patch('app.callbooker._google.AdminGoogleCalendar._create_resource') async def test_support_call_book_contact_exists_no_email(self, mock_gcal_builder, mock_add_task): @@ -623,7 +695,7 @@ async def test_support_call_book_contact_exists_no_email(self, mock_gcal_builder ) company = await Company.create(name='Julies Ltd', country='GB', sales_person=admin) - contact = await Contact.create(first_name='B', last_name='Junes', company_id=company.id) + await Contact.create(first_name='B', last_name='Junes', company_id=company.id) meeting_data.update(company_id=company.id, admin_id=admin.id) r = await self.client.post('/callbooker/support/book/', json=meeting_data) @@ -631,6 +703,7 @@ async def test_support_call_book_contact_exists_no_email(self, mock_gcal_builder company = await Company.get() assert company.name == 'Julies Ltd' + assert company.support_call_count == 1 contact = await Contact.get() assert contact.first_name == 'B' diff --git a/tests/test_combined.py b/tests/test_combined.py index 8527babc..0874f360 100644 --- a/tests/test_combined.py +++ b/tests/test_combined.py @@ -615,7 +615,7 @@ async def test_cb_client_event_company_org_deal(self, mock_pd_request): assert company.paid_invoice_count == 0 assert await company.bdr_person == await company.support_person == await company.sales_person == admin assert company.has_signed_up - assert not company.has_booked_call + assert company.sales_call_count == 0 assert company.estimated_income @@ -746,7 +746,7 @@ async def test_com_cli_create_update_org_deal(self, mock_gcal_builder, mock_pd_r assert company.estimated_income == '1000' assert not company.support_person assert not company.bdr_person - assert company.has_booked_call + assert company.sales_call_count == 1 assert await company.sales_person == sales_person assert not company.support_person_id diff --git a/tests/test_hermes.py b/tests/test_hermes.py index 7e3481e0..eafe88aa 100644 --- a/tests/test_hermes.py +++ b/tests/test_hermes.py @@ -623,7 +623,8 @@ async def test_get_companies_by_id(self): 'paid_invoice_count': 0, 'estimated_income': None, 'currency': None, - 'has_booked_call': False, + 'sales_call_count': 0, + 'support_call_count': 0, 'has_signed_up': False, 'utm_campaign': None, 'utm_source': None, @@ -685,7 +686,8 @@ async def test_get_companies_by_tc2_id(self): 'paid_invoice_count': 0, 'estimated_income': None, 'currency': None, - 'has_booked_call': False, + 'sales_call_count': 0, + 'support_call_count': 0, 'has_signed_up': False, 'utm_campaign': None, 'utm_source': None, diff --git a/tests/test_pipedrive.py b/tests/test_pipedrive.py index e979394d..235d4138 100644 --- a/tests/test_pipedrive.py +++ b/tests/test_pipedrive.py @@ -402,7 +402,7 @@ async def test_support_call_booked_org_exists(self, mock_request): name='Julies Ltd', website='https://junes.com', country='GB', pd_org_id=10, sales_person=admin ) self.pipedrive.db['organizations'] = { - 1: { + 10: { 'id': 10, 'name': 'Julies Ltd', 'address_country': 'GB', @@ -424,7 +424,7 @@ async def test_support_call_booked_org_exists(self, mock_request): ) await pd_post_process_support_call(contact, meeting) assert self.pipedrive.db['organizations'] == { - 1: { + 10: { 'id': 10, 'name': 'Julies Ltd', 'address_country': 'GB', @@ -449,6 +449,7 @@ async def test_support_call_booked_org_exists(self, mock_request): assert not await Deal.exists() assert self.pipedrive.db['activities'] == { 1: { + 'id': 1, 'due_date': '2023-01-01', 'due_time': '00:00', 'subject': 'TutorCruncher demo with Steve Jobs', @@ -456,7 +457,6 @@ async def test_support_call_booked_org_exists(self, mock_request): 'deal_id': None, 'person_id': 1, 'org_id': 10, - 'id': 1, }, } diff --git a/tests/test_tc2.py b/tests/test_tc2.py index cac7d864..de976bfe 100644 --- a/tests/test_tc2.py +++ b/tests/test_tc2.py @@ -259,7 +259,7 @@ async def test_cb_client_event_test_1(self, mock_add_task): assert company.paid_invoice_count == 0 assert await company.bdr_person == await company.support_person == await company.sales_person == admin assert company.has_signed_up - assert not company.has_booked_call + assert company.sales_call_count == 0 assert not company.estimated_income @@ -332,7 +332,7 @@ async def test_cb_client_event_test_3(self, mock_add_task): assert company.paid_invoice_count == 2 assert await company.bdr_person == await company.sales_person == admin assert company.has_signed_up - assert not company.has_booked_call + assert company.sales_call_count == 0 assert not await company.support_person assert not company.estimated_income @@ -372,7 +372,7 @@ async def test_cb_client_event_test_4(self, mock_add_task): assert company.country == 'GB' assert company.paid_invoice_count == 2 assert company.has_signed_up - assert not company.has_booked_call + assert company.sales_call_count == 0 assert not await company.support_person assert await company.bdr_person == await company.sales_person == admin