from __future__ import annotations from .crm_mixin import CrmMixin from .crm_shared import ( CUSTOMER_STATUS_LABELS, FOLLOWUP_CONTENT_KEYS, FOLLOWUP_DATE_KEYS, FOLLOWUP_STATUS_LABELS, FOLLOWUP_TYPE_LABELS, UNKNOWN_NAME_PREFIX, Any, Mapping, _beijing_datetime, _compact_field_list, _fields_with_projections, _first_text, _first_value, _prune, _ref_ids, _relation_refs, ) class CrmPresentersMixin(CrmMixin): def _project_fields(self) -> list[str]: project = self.config.project scalars = [ project.id_field, project.name_field, project.customer_field, project.inquiry_date_field, project.progress_field, project.applications_field, project.part_numbers_field, project.period_usage_field, project.details_field, project.attachment_field, "createdAt", "updatedAt", ] projections = [ *self._projection(project.customer_relation_field, self._customer_spec(), None), *self._projection(project.contact_field, self._contact_spec(), None), ] return _fields_with_projections(scalars, projections) def _compact_project(self, row: Mapping[str, Any], *, include_raw: bool) -> dict[str, Any]: project = self.config.project progress = row.get(project.progress_field) progress_labels = {value: label for label, value in project.progress_values.items()} compact: dict[str, Any] = { "id": row.get(project.id_field), "name": row.get(project.name_field), "customer_id": row.get(project.customer_field), "customers": _relation_refs(row, [project.customer_relation_field], self._customer_spec()), "inquiry_date": row.get(project.inquiry_date_field), "stage": progress_labels.get(str(progress), progress) if progress is not None else None, "applications": row.get(project.applications_field), "part_numbers": row.get(project.part_numbers_field), "period_usage": row.get(project.period_usage_field), "details": row.get(project.details_field), "contacts": _relation_refs(row, [project.contact_field], self._contact_spec()), "createdAt": _beijing_datetime(row.get("createdAt"), self.config.timezone), "updatedAt": _beijing_datetime(row.get("updatedAt"), self.config.timezone), } compact = _prune(compact) if include_raw: compact["raw"] = dict(row) return compact def _compact_project_update(self, row: Mapping[str, Any]) -> dict[str, Any]: creators = _relation_refs(row, ["createdBy"], self._user_spec()) return _prune( { "id": row.get("id"), "updated_at": _beijing_datetime( row.get(self.config.project.update_date_field) or row.get("createdAt"), self.config.timezone, ), "content": row.get(self.config.project.update_content_field), "creator": creators[0] if creators else None, } ) def _compact_customers(self, rows: list[dict[str, Any]], *, include_raw: bool = False) -> list[dict[str, Any]]: return [self._compact_customer(row, include_raw=include_raw) for row in rows] def _customer_name_candidates(self) -> list[str]: return [*(self.config.customer.name_fields or []), "name", "title"] def _customer_compact_fields(self, *, detail: bool = False) -> list[str]: customer = self.config.customer scalars = _compact_field_list( customer.fields, [ customer.id_field, *self._customer_name_candidates(), customer.english_name_field, customer.territorial_name_field, customer.status_field, customer.source_field, "createdAt", "updatedAt", *(customer.detail_fields.values() if detail else []), ], ) projections = [ *self._projection(customer.owner_field, self._user_spec(), customer.fields), *self._projection(customer.manager_field, self._user_spec(), customer.fields), ] return _fields_with_projections(scalars, projections) def _compact_customer( self, row: Mapping[str, Any], *, detail: bool = False, include_raw: bool = False, ) -> dict[str, Any]: customer = self.config.customer identifier = row.get(customer.id_field) name = _first_text(row, self._customer_name_candidates()) if name is None and identifier is not None: name = f"{UNKNOWN_NAME_PREFIX}{identifier}" english_name = _first_text(row, [customer.english_name_field, "english_name"]) territorial_name = _first_text(row, [customer.territorial_name_field, "territorial_name"]) status = _first_value(row, [customer.status_field, "status"]) compact: dict[str, Any] = { "id": identifier, "name": name, # Only worth repeating when they carry information the name does not. "english_name": english_name if english_name != name else None, "territorial_name": territorial_name if territorial_name != name else None, "status": CUSTOMER_STATUS_LABELS.get(status, str(status) if status is not None else None), "source": _first_value(row, [customer.source_field, "source"]), "owners": _relation_refs(row, [customer.owner_field], self._user_spec()), "managers": _relation_refs(row, [customer.manager_field], self._user_spec()), "createdAt": _beijing_datetime(row.get("createdAt"), self.config.timezone), "updatedAt": _beijing_datetime(row.get("updatedAt"), self.config.timezone), } if detail: for output_key, source_field in customer.detail_fields.items(): if output_key not in compact: compact[output_key] = row.get(source_field) compact = _prune(compact) if include_raw: compact["raw"] = dict(row) return compact def _compact_contacts( self, rows: list[dict[str, Any]], *, include_details: bool = True, include_raw: bool = False, ) -> list[dict[str, Any]]: return [self._compact_contact(row, include_details=include_details, include_raw=include_raw) for row in rows] def _contact_name_candidates(self) -> list[str]: return [*(self.config.contact.name_fields or []), "name", "title"] def _contact_compact_fields(self, *, include_details: bool = True) -> list[str]: contact = self.config.contact scalars = _compact_field_list( contact.fields, [ contact.id_field, *self._contact_name_candidates(), contact.title_field, "position", *([contact.phone_field, "phone", contact.email_field, "email"] if include_details else []), "createdAt", "updatedAt", ], ) projections = self._projection(contact.customer_field, self._customer_spec(), contact.fields) return _fields_with_projections(scalars, projections) def _compact_contact( self, row: Mapping[str, Any], *, include_details: bool = True, include_raw: bool = False, ) -> dict[str, Any]: contact = self.config.contact compact: dict[str, Any] = { "id": row.get(contact.id_field), "name": _first_text(row, self._contact_name_candidates()), "title": _first_text(row, [contact.title_field, "title", "position"]), "customers": _relation_refs(row, [contact.customer_field], self._customer_spec()), "createdAt": _beijing_datetime(row.get("createdAt"), self.config.timezone), "updatedAt": _beijing_datetime(row.get("updatedAt"), self.config.timezone), } if include_details: compact["phone"] = _first_text(row, [contact.phone_field, "phone"]) compact["email"] = _first_text(row, [contact.email_field, "email"]) compact = _prune(compact) if include_raw: compact["raw"] = dict(row) return compact def _compact_followups(self, rows: list[dict[str, Any]], *, include_raw: bool = False) -> list[dict[str, Any]]: return [self._compact_followup(row, include_raw=include_raw) for row in rows] def _followup_content_fields(self) -> list[str]: return [ self.config.followup.content_field, *(self.config.followup.search_fields or []), *FOLLOWUP_CONTENT_KEYS, ] def _followup_compact_fields(self) -> list[str]: followup = self.config.followup scalars = _compact_field_list( followup.fields, [ followup.id_field, followup.date_field, *FOLLOWUP_DATE_KEYS, *self._followup_content_fields(), followup.type_field, followup.status_field, followup.location_field, ], ) projections = [ *self._projection(followup.customer_field, self._customer_spec(), followup.fields), *self._projection(followup.contact_field, self._contact_spec(), followup.fields), *self._projection(followup.project_field, self._project_spec(), followup.fields), *self._projection(followup.owner_field, self._user_spec(), followup.fields), *self._projection("createdBy", self._user_spec(), followup.fields), ] return _fields_with_projections(scalars, projections) def _compact_followup(self, row: Mapping[str, Any], *, include_raw: bool = False) -> dict[str, Any]: followup = self.config.followup summary = self._followup_summary(row) participants = summary["participants"] followup_type = row.get(followup.type_field) record_status = row.get(followup.status_field) compact: dict[str, Any] = { "id": row.get(followup.id_field), "followedAt": _beijing_datetime(summary["followedAt"], self.config.timezone), "customers": summary["customers"], "contacts": summary["contacts"], "participants": participants, "projects": summary["projects"], "type": FOLLOWUP_TYPE_LABELS.get(str(followup_type), followup_type) if followup_type is not None else None, "status": ( FOLLOWUP_STATUS_LABELS.get(str(record_status), record_status) if record_status is not None else None ), "location": _first_text(row, [followup.location_field]), "content": summary["content"], } # A record is usually submitted by one of its own participants; naming the # submitter again in that case is pure repetition, so it only shows up when # it actually tells the reader something new. creator = summary["creator"] if creator and creator.get("id") not in _ref_ids(participants): compact["creator"] = creator compact = _prune(compact) if include_raw: compact["raw"] = dict(row) return compact def _followup_summary(self, row: Mapping[str, Any]) -> dict[str, Any]: followup = self.config.followup date_fields = [followup.date_field, *FOLLOWUP_DATE_KEYS] # owner_fields drives *filtering* and may include createdBy; participants # are only ever the follow-up person relation (the contribution basis). participant_fields = [followup.owner_field] creators = _relation_refs(row, ["createdBy"], self._user_spec()) return { "content": _first_text(row, self._followup_content_fields()), "followedAt": _first_text(row, date_fields), "customers": _relation_refs(row, [followup.customer_field], self._customer_spec()), "contacts": _relation_refs(row, [followup.contact_field], self._contact_spec()), "participants": _relation_refs(row, participant_fields, self._user_spec()), "creator": creators[0] if creators else None, "projects": _relation_refs(row, [followup.project_field], self._project_spec()), }