diff --git a/infrastructure/hubspot/deal_properties_client.py b/infrastructure/hubspot/deal_properties_client.py index d6f72b9e6..f97a51448 100644 --- a/infrastructure/hubspot/deal_properties_client.py +++ b/infrastructure/hubspot/deal_properties_client.py @@ -24,7 +24,13 @@ class HubspotDealPropertiesClient: a property whole (HubSpot has no append) would otherwise overwrite edits made since the last scrape. """ - raise NotImplementedError + deal: Any = scrubbed_call_with_retry( + lambda: self._client.crm.deals.basic_api.get_by_id( # type: ignore[reportUnknownMemberType] + deal_id, properties=[property_name] + ) + ) + properties = cast(Dict[str, Optional[str]], deal.properties) + return properties.get(property_name) def write_deal_property( self, deal_id: str, property_name: str, value: str diff --git a/orchestration/abri_orchestrator.py b/orchestration/abri_orchestrator.py index dfbf2dd80..c9fda15f5 100644 --- a/orchestration/abri_orchestrator.py +++ b/orchestration/abri_orchestrator.py @@ -270,7 +270,7 @@ class AbriOrchestrator: return tenancy if isinstance(tenancy, NoTenancyFound): - self._note_property_void(deal_id=deal_id, place_ref=place_ref) + self._note_property_void(deal_id=deal_id) return PropertyReportedVoid(place_ref=place_ref) contact_ids: List[str] = [] @@ -304,15 +304,30 @@ class AbriOrchestrator: ), ) - def _note_property_void(self, deal_id: str, place_ref: PlaceRef) -> None: - """Record the void property in the deal's extra booking information. + def _note_property_void(self, deal_id: str) -> None: + """Flag the void property at the front of the deal's booking information. - Deliberately unimplemented: the extra-booking-information property is - not yet scraped onto the deal, so there is nothing to append to. The - write lands in the follow-up that adds the property; until then the - flow completes so a void property never dead-letters. Filling this in - is a single write_deal_property call with VOID_PROPERTY_NOTE. + HubSpot has no append, so the property is read and rewritten whole. + The read is live rather than from the scraped snapshot: the snapshot + lags by a scrape cycle, and rewriting from it would discard whatever a + coordinator typed in the meantime. The write is still last-write-wins + — HubSpot offers no compare-and-set — so the race is narrowed, not + closed. """ + existing = ( + self._deal_properties.read_deal_property( + deal_id=deal_id, property_name=NOTES_FOR_SURVEYOR_DEAL_PROPERTY + ) + or "" + ) + if existing.startswith(VOID_MARKER): + return + + self._deal_properties.write_deal_property( + deal_id=deal_id, + property_name=NOTES_FOR_SURVEYOR_DEAL_PROPERTY, + value=f"{VOID_MARKER} {existing}".rstrip(), + ) def amend_job(self, change: AppointmentChange) -> AmendJobOrchestrationResult: job_no = self._deal_database.job_no_for_deal(change.deal_id)