diff options
Diffstat (limited to 'init.el')
| -rw-r--r-- | init.el | 773 |
1 files changed, 772 insertions, 1 deletions
@@ -2,12 +2,20 @@ ;;; Code: +(require 'abbrev) +(require 'org) +(require 'org-element) +(require 'seq) +(require 'subr-x) + ;; -------------------------------------------------- ;; Core paths ;; -------------------------------------------------- (setq org-directory (expand-file-name "~/org")) -(setq org-default-notes-file (expand-file-name "obtf.org" org-directory)) +(setq org-default-notes-file (expand-file-name "optus.org" org-directory)) +(defconst ss-crm-file (expand-file-name "crm.org" org-directory) + "Root CRM file for people cards.") (setq custom-file (expand-file-name "custom.el" (file-name-directory @@ -20,6 +28,8 @@ (push org-directory missing-paths)) (unless (file-exists-p org-default-notes-file) (push org-default-notes-file missing-paths)) + (unless (file-exists-p ss-crm-file) + (push ss-crm-file missing-paths)) (when missing-paths (display-warning 'ss-org @@ -150,6 +160,767 @@ ("C-c o" . consult-outline))) ;; -------------------------------------------------- +;; CRM +;; -------------------------------------------------- + +(defconst ss-crm-relationship-options + '("Peer" + "Direct Report" + "Internal Stakeholder" + "External Stakeholder" + "Vendor") + "Built-in relationship values for CRM cards.") + +(defconst ss-crm-engagement-options + '("Perm" "SOW" "SOW Fixed Outcome" "NCS India") + "Built-in engagement values for CRM cards.") + +(defconst ss-crm-role-options + '("Account Manager" + "AD Platform Manager" + "AD, Billing & Catalogue, Engineering" + "AD, Frontend Engineering" + "AD, IT Core & MarTech" + "AD, Mobile Engineering" + "AD, Platform Engineering" + "AD, Portfolio Delivery Products and API Management" + "AD, Portfolio Mgmt. Customer Engagement" + "AD, Quality Engineering" + "AD, Tech Product Owner" + "AD, Technical Delivery" + "AD, Technical Platform Management" + "AD, Technical Platform Manager, API" + "AD, Technical Platform Manager, Catalog" + "AD, Technical Platform Manager, Order and Quote" + "AD, Technical Product Management" + "Agile Scrum Master" + "Analyst Programmer - Senior" + "Android Developer" + "Android Developer - Senior" + "Application Development Lead" + "Architect" + "Associate Engineer" + "Associate Engineer" + "Associate Engineer, Frontend" + "Associate Engineer, Platform Engineering" + "Associate Software Engineer" + "Billing Executive" + "Business Analyst" + "Business Analyst - Senior" + "Business Development Manager" + "Business Intelligence Analyst" + "Business Program Manager" + "Cloud Engineer" + "Consultant" + "CRM Developer" + "Data Analyst" + "Data Analyst - Senior" + "Delivery Lead" + "Delivery Manager" + "Demand Lead" + "Deployment Manager" + "Development Manager" + "DevOps Engineer" + "DevOps Engineer - Senior" + "Digital Delivery Manager" + "Director Customer Engagement" + "Director Sales Technology" + "Director, Billing and Charging" + "Director, Delivery Portfolio Billing and Charging" + "Director, Digital Engineering" + "Director, Portfolio Mgmt." + "Director, SF Comms Technical Platform" + "Director, SSF Core & Commerce" + "Engineer" + "Engineer - Junior" + "Engineer - Senior" + "Engineer, Platform Engineering" + "Engineer, Salesforce" + "Engineering Manager" + "Engineering Manager, Mobile" + "Engineering Manager, Platform Engineering" + "Executive Assistant" + "Integration Lead" + "Integration Solution Designer" + "Integration Technical Specialist" + "iOS Developer" + "iOS Developer - Senior" + "IT Business Partner" + "Lead Consultant" + "Lead Developer" + "Manager, Release Engineer" + "Manager, Release Train Engineer" + "Mobile API Developer" + "Optus Tech Graduate" + "Portfolio Manager" + "Process Analyst - Senior" + "Product Manager" + "Product Solution Designer" + "Program Analyst" + "Program Director" + "Program Manager" + "Project Architect" + "Project Coordinator" + "Project Manager" + "Quality Assurance Analyst" + "Quality Engineering Professional" + "Release Coordinator" + "Release Manager" + "Salesforce Administrator" + "Salesforce Developer (Heroku, Lightning, etc)" + "Salesforce Marketing Cloud Architect" + "Salesforce Operations Manager" + "Scrum Master" + "SD, Engineering Capability" + "SD, Marketing Technology" + "SD, Technical Products" + "Senior Business Analyst" + "Senior Delivery Manager" + "Senior Delivery Manger" + "Senior Digital Technical Producer" + "Senior E2E Business Analyst Lead" + "Senior Engineer" + "Senior Engineer, API Engineering" + "Senior Engineer, Billing & Catalogue" + "Senior Engineer, Platform Engineering" + "Senior Engineer, Salesforce" + "Senior Front End Developer" + "Senior Integration Solution Designer" + "Senior Quality Engineering Professional" + "Senior Salesforce Administrator" + "Senior Salesforce Consultant" + "Senior Salesforce Functional Consultant" + "Senior Salesforce Operations Lead" + "Senior Software Developer" + "Senior Software Engineer" + "Senior Software Engineer, Frontend" + "Senior Software Engineer, Mobile" + "Senior Solution Designer" + "Snr Dir, Delivery & Platform Manager" + "Snr Director, Portfolio Mgmt. & Delivery" + "Software Developer" + "Software Developer - Junior" + "Software Developer - Senior" + "Software Engineer" + "Software Engineer, Frontend" + "Software Engineer, Mobile" + "Solution Architect" + "Solution Designer" + "Solution Designer - Senior" + "Staff Engineer" + "Support Analyst" + "System Analyst" + "System Engineer" + "Technical Lead" + "Technical Platform Manager" + "Technical Platform Manager, Billing and Charging" + "Technical Product Manager" + "Technical Project Manager - Senior" + "Technical Specialist - Senior" + "Technology Delivery Analyst" + "Test Analyst" + "Test Lead" + "Test Manager" + "VP, EB IT") + "Built-in role values for CRM cards.") + +(defconst ss-crm-supplier-options + '("Accenture Song" + "INFOSYS TECHNOLOGIES LIMITED" + "MAKK Integrations Pty Ltd" + "NCSI Technologies India Private Limited" + "TECH MAHINDRA LTD") + "Built-in supplier values for CRM cards.") + +(defvar ss-crm--cache nil + "Cached CRM entries.") + +(defvar ss-crm--cache-mtime nil + "Modification time of the cached CRM file.") + +(defvar ss-crm-prefix-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "o") #'ss-crm-open) + (define-key map (kbd "f") #'ss-crm-find) + (define-key map (kbd "a") #'ss-crm-add) + (define-key map (kbd "i") #'ss-crm-insert-name) + (define-key map (kbd "s") #'ss-crm-insert-summary) + map) + "Keymap for CRM commands.") + +(keymap-global-set "C-c p" ss-crm-prefix-map) + +(defun ss-crm-file-available-p () + "Return non-nil when the CRM file exists." + (file-exists-p ss-crm-file)) + +(defun ss-crm-require-file () + "Return `ss-crm-file', signaling when it does not exist." + (unless (ss-crm-file-available-p) + (user-error "CRM file does not exist: %s" ss-crm-file)) + ss-crm-file) + +(defun ss-crm-source-buffer-p () + "Return non-nil when the current buffer visits `ss-crm-file'." + (and buffer-file-name + (string= (expand-file-name buffer-file-name) + (expand-file-name ss-crm-file)))) + +(defun ss-crm-entry-get (entry property) + "Return PROPERTY from ENTRY." + (plist-get entry property)) + +(defun ss-crm-default-abbrev (name) + "Suggest a short abbrev trigger for NAME." + (let* ((parts (split-string (string-trim name) "[[:space:]]+" t)) + (first (downcase (substring (car parts) 0 (min 2 (length (car parts)))))) + (last (downcase (substring (car (last parts)) 0 1)))) + (if (> (length parts) 1) + (concat ";" first last) + (concat ";" first)))) + +(defun ss-crm-split-values (value) + "Split comma-separated VALUE into trimmed strings." + (when (and value (not (string-empty-p value))) + (seq-filter + (lambda (item) + (not (string-empty-p item))) + (mapcar #'string-trim (split-string value "," t))))) + +(defun ss-crm-property-line (property value) + "Return an Org property line for PROPERTY and VALUE." + (format ":%s:%s\n" + property + (if (and value (not (string-empty-p value))) + (concat " " value) + ""))) + +(defun ss-crm-parse-entry-at-point (headline) + "Return the CRM card described by HEADLINE at point." + (list :name (org-element-property :raw-value headline) + :abbrev (org-entry-get nil "ABBREV") + :aliases (ss-crm-split-values (org-entry-get nil "ALIASES")) + :role (org-entry-get nil "ROLE") + :team (org-entry-get nil "TEAM") + :manager (org-entry-get nil "MANAGER") + :relationship (org-entry-get nil "RELATIONSHIP") + :engagement (org-entry-get nil "ENGAGEMENT") + :supplier (org-entry-get nil "SUPPLIER") + :location (org-entry-get nil "LOCATION") + :current-focus (org-entry-get nil "CURRENT_FOCUS"))) + +(defun ss-crm-parse-entries () + "Parse top-level CRM cards from `ss-crm-file'." + (with-temp-buffer + (insert-file-contents (ss-crm-require-file)) + (delay-mode-hooks + (org-mode)) + (let ((ast (org-element-parse-buffer)) + cards) + (org-element-map ast 'headline + (lambda (headline) + (when (= 1 (org-element-property :level headline)) + (goto-char (org-element-property :begin headline)) + (push (ss-crm-parse-entry-at-point headline) cards)))) + (sort cards + (lambda (left right) + (string< (ss-crm-entry-get left :name) + (ss-crm-entry-get right :name))))))) + +(defun ss-crm-entries () + "Return cached CRM cards, refreshing when the file changes." + (when (ss-crm-file-available-p) + (let* ((attributes (file-attributes ss-crm-file)) + (mtime (file-attribute-modification-time attributes))) + (unless (and ss-crm--cache + (equal mtime ss-crm--cache-mtime)) + (setq ss-crm--cache (ss-crm-parse-entries) + ss-crm--cache-mtime mtime)) + ss-crm--cache))) + +(defun ss-crm-entry-by-name (name) + "Return the CRM card whose canonical name is NAME." + (seq-find + (lambda (entry) + (string= name (ss-crm-entry-get entry :name))) + (ss-crm-entries))) + +(defun ss-crm-search-keys (entry) + "Return canonical and alias search keys for ENTRY." + (cons (ss-crm-entry-get entry :name) + (ss-crm-entry-get entry :aliases))) + +(defun ss-crm-match-p (query entry) + "Return non-nil when QUERY matches ENTRY name or aliases." + (let* ((parts (split-string (downcase (string-trim query)) "[[:space:]]+" t)) + (keys (mapcar #'downcase (ss-crm-search-keys entry)))) + (seq-every-p + (lambda (part) + (seq-some + (lambda (key) + (string-match-p (regexp-quote part) key)) + keys)) + parts))) + +(defun ss-crm-matching-entries (query) + "Return entries whose canonical names or aliases match QUERY." + (let ((entries (ss-crm-entries))) + (if (string-empty-p (string-trim query)) + entries + (seq-filter + (lambda (entry) + (ss-crm-match-p query entry)) + entries)))) + +(defun ss-crm-summary (entry) + "Return a compact summary string for ENTRY." + (string-join + (seq-filter + (lambda (value) + (and value (not (string-empty-p value)))) + (list (ss-crm-entry-get entry :role) + (ss-crm-entry-get entry :team) + (ss-crm-entry-get entry :relationship) + (ss-crm-entry-get entry :engagement) + (ss-crm-entry-get entry :current-focus))) + " | ")) + +(defun ss-crm-display (entry) + "Return a compact display string for ENTRY." + (let ((summary (ss-crm-summary entry))) + (if (string-empty-p summary) + (ss-crm-entry-get entry :name) + (format "%s %s" (ss-crm-entry-get entry :name) summary)))) + +(defun ss-crm-completion-table (string pred action) + "Complete canonical CRM names while matching aliases via STRING." + (if (eq action 'metadata) + '(metadata (category . ss-crm-person)) + (complete-with-action + action + (mapcar + (lambda (entry) + (ss-crm-entry-get entry :name)) + (ss-crm-matching-entries string)) + string + pred))) + +(defun ss-crm-marginalia-annotator (candidate) + "Return a Marginalia annotation for CRM CANDIDATE." + (when-let ((entry (ss-crm-entry-by-name candidate))) + (concat " " (ss-crm-summary entry)))) + +(with-eval-after-load 'marginalia + (when (boundp 'marginalia-annotator-registry) + (add-to-list 'marginalia-annotator-registry + '(ss-crm-person ss-crm-marginalia-annotator nil nil)))) + +(defun ss-crm-select-entry (&optional prompt) + "Select a CRM card using PROMPT." + (let ((completion-extra-properties + '(:annotation-function ss-crm-marginalia-annotator))) + (when-let ((name (completing-read (or prompt "Person: ") + #'ss-crm-completion-table + nil + t))) + (ss-crm-entry-by-name name)))) + +(defun ss-crm-read-string (prompt &optional default) + "Read PROMPT and trim the result." + (string-trim (read-string prompt nil nil default))) + +(defun ss-crm-read-required-string (prompt &optional default) + "Read PROMPT and require a non-empty result." + (let ((value (ss-crm-read-string prompt default))) + (if (string-empty-p value) + (user-error "%s is required" (string-remove-suffix ": " prompt)) + value))) + +(defun ss-crm-known-property-values (property) + "Return sorted unique non-empty values for PROPERTY from CRM cards." + (seq-sort + #'string< + (delete-dups + (seq-filter + (lambda (value) + (and (stringp value) + (not (string-empty-p (string-trim value))))) + (mapcar + (lambda (entry) + (ss-crm-entry-get entry property)) + (ss-crm-entries)))))) + +(defun ss-crm-known-person-names () + "Return sorted canonical CRM names." + (seq-sort + #'string< + (delete-dups + (seq-filter + (lambda (name) + (and (stringp name) + (not (string-empty-p (string-trim name))))) + (mapcar + (lambda (entry) + (ss-crm-entry-get entry :name)) + (ss-crm-entries)))))) + +(defun ss-crm-lookup-values (property &optional seeded) + "Return sorted unique PROPERTY values merged with SEEDED values." + (seq-sort + #'string< + (delete-dups + (seq-filter + (lambda (value) + (and (stringp value) + (not (string-empty-p (string-trim value))))) + (append seeded + (ss-crm-known-property-values property)))))) + +(defun ss-crm-read-choice-warning (prompt similar) + "Warn that PROMPT value is similar to existing SIMILAR." + (display-warning + 'ss-crm + (format "%sA similar existing value already exists: %s" prompt similar) + :warning)) + +(defun ss-crm-read-choice (prompt choices &rest plist) + "Read PROMPT from CHOICES using options in PLIST." + (let* ((allow-blank (plist-get plist :allow-blank)) + (allow-new (plist-get plist :allow-new)) + (require-match (plist-get plist :require-match)) + (blank-choice "[none]") + (collection (if allow-blank + (cons blank-choice choices) + choices)) + (completion-require-match (if allow-new nil require-match))) + (catch 'done + (while t + (let* ((value (completing-read prompt collection nil completion-require-match)) + (existing (seq-find + (lambda (choice) + (string= value choice)) + choices))) + (cond + ((or (string-empty-p value) + (and allow-blank + (string= value blank-choice))) + (throw 'done nil)) + (existing + (throw 'done existing)) + ((not allow-new) + (user-error "Please choose an existing value")) + (t + (when-let ((similar + (seq-find + (lambda (choice) + (and (not (string= value choice)) + (string-equal (downcase value) + (downcase choice)))) + choices))) + (ss-crm-read-choice-warning prompt similar)) + (when (yes-or-no-p (format "Create new value `%s'? " value)) + (throw 'done value))))))))) + +(defun ss-crm-read-role () + "Read a CRM role." + (ss-crm-read-choice "Role: " + (ss-crm-lookup-values :role ss-crm-role-options) + :allow-blank t + :allow-new t)) + +(defun ss-crm-read-team () + "Read a CRM team." + (ss-crm-read-choice "Team: " + (ss-crm-lookup-values :team) + :allow-blank t + :allow-new t)) + +(defun ss-crm-read-manager () + "Read a CRM manager from existing CRM names." + (ss-crm-read-choice "Manager: " + (ss-crm-known-person-names) + :allow-blank t + :require-match t)) + +(defun ss-crm-read-relationship () + "Read a CRM relationship value." + (ss-crm-read-choice "Relationship: " + (ss-crm-lookup-values :relationship + ss-crm-relationship-options) + :allow-blank t + :allow-new t)) + +(defun ss-crm-read-engagement () + "Read a CRM engagement." + (ss-crm-read-choice "Engagement: " + (ss-crm-lookup-values :engagement + ss-crm-engagement-options) + :allow-blank t + :allow-new t)) + +(defun ss-crm-read-supplier () + "Read a CRM supplier." + (ss-crm-read-choice "Supplier: " + (ss-crm-lookup-values :supplier + ss-crm-supplier-options) + :allow-blank t + :allow-new t)) + +(defun ss-crm-read-location () + "Read a CRM location." + (ss-crm-read-choice "Location: " + (ss-crm-lookup-values :location) + :allow-blank t + :allow-new t)) + +(defun ss-crm-reload () + "Clear cached CRM data and refresh prose buffers." + (interactive) + (setq ss-crm--cache nil + ss-crm--cache-mtime nil) + (ss-crm-refresh-buffers) + (message "Reloaded CRM")) + +(defun ss-crm-open-entry (entry) + "Open `ss-crm-file' and narrow to ENTRY." + (find-file (ss-crm-require-file)) + (widen) + (let ((position (org-find-exact-headline-in-buffer + (ss-crm-entry-get entry :name)))) + (unless position + (user-error "No CRM card for %s" (ss-crm-entry-get entry :name))) + (goto-char position)) + (org-narrow-to-subtree) + (org-fold-show-subtree) + (org-fold-show-entry) + (goto-char (point-min))) + +(defun ss-crm-open () + "Open `ss-crm-file' in overview mode." + (interactive) + (find-file (ss-crm-require-file)) + (widen) + (goto-char (point-min)) + (org-overview) + (org-cycle-hide-drawers 'all)) + +(defun ss-crm-find () + "Select a CRM card and open it." + (interactive) + (ss-crm-open-entry + (or (ss-crm-select-entry "Find person: ") + (user-error "No person selected")))) + +(defun ss-crm-insert-name () + "Insert a canonical CRM name at point." + (interactive) + (let ((entry (or (ss-crm-select-entry "Insert person name: ") + (user-error "No person selected")))) + (insert (ss-crm-entry-get entry :name)))) + +(defun ss-crm-insert-summary () + "Insert a compact CRM summary at point." + (interactive) + (let ((entry (or (ss-crm-select-entry "Insert person summary: ") + (user-error "No person selected")))) + (insert (ss-crm-display entry)))) + +(defun ss-crm-add () + "Add a new CRM card to `ss-crm-file'." + (interactive) + (let ((name (ss-crm-read-required-string "Full name: ")) + (abbrev nil) + (aliases nil) + (role nil) + (team nil) + (manager nil) + (relationship nil) + (engagement nil) + (supplier nil) + (location nil) + (current-focus nil)) + (when (ss-crm-entry-by-name name) + (user-error "A CRM card for %s already exists" name)) + (setq abbrev (ss-crm-read-string "Abbrev: " (ss-crm-default-abbrev name)) + aliases (ss-crm-read-string "Aliases (comma-separated, optional): ") + role (ss-crm-read-role) + team (ss-crm-read-team) + manager (ss-crm-read-manager) + relationship (ss-crm-read-relationship) + engagement (ss-crm-read-engagement) + supplier (ss-crm-read-supplier) + location (ss-crm-read-location) + current-focus (ss-crm-read-string "Current focus: ")) + (when (string-empty-p abbrev) + (setq abbrev (ss-crm-default-abbrev name))) + (find-file (ss-crm-require-file)) + (widen) + (goto-char (point-max)) + (unless (bolp) + (insert "\n")) + (unless (looking-back "\n\n" nil) + (insert "\n")) + (insert "* " name "\n" + ":PROPERTIES:\n" + (ss-crm-property-line "ABBREV" abbrev) + (ss-crm-property-line "ALIASES" aliases) + (ss-crm-property-line "ROLE" role) + (ss-crm-property-line "TEAM" team) + (ss-crm-property-line "MANAGER" manager) + (ss-crm-property-line "RELATIONSHIP" relationship) + (ss-crm-property-line "ENGAGEMENT" engagement) + (ss-crm-property-line "SUPPLIER" supplier) + (ss-crm-property-line "LOCATION" location) + (ss-crm-property-line "CURRENT_FOCUS" current-focus) + ":END:\n\n" + "** Context\n\n" + "** TODOs\n") + (save-buffer) + (ss-crm-reload) + (ss-crm-open-entry (ss-crm-entry-by-name name)))) + +(defun ss-crm-clear-installed-abbrevs () + "Remove CRM abbrevs from the current local abbrev table." + (mapatoms + (lambda (symbol) + (when (abbrev-get symbol :ss/crm) + (define-abbrev local-abbrev-table (symbol-name symbol) nil))) + local-abbrev-table)) + +(defun ss-crm-install-abbrevs () + "Install CRM abbrevs into the current prose buffer." + (when (and (not (ss-crm-source-buffer-p)) + (ss-crm-file-available-p)) + (setq-local local-abbrev-table (copy-abbrev-table local-abbrev-table)) + (ss-crm-clear-installed-abbrevs) + (dolist (entry (ss-crm-entries)) + (let* ((name (ss-crm-entry-get entry :name)) + (abbrev (ss-crm-entry-get entry :abbrev)) + (abbrev-name + (if (and abbrev (not (string-empty-p abbrev))) + abbrev + (ss-crm-default-abbrev name)))) + (define-abbrev local-abbrev-table abbrev-name name) + (when-let ((symbol (abbrev-symbol abbrev-name local-abbrev-table))) + (abbrev-put symbol :ss/crm t)))))) + +(defun ss-crm-refresh-buffers () + "Refresh CRM abbrevs in text and Org buffers." + (dolist (buffer (buffer-list)) + (with-current-buffer buffer + (when (and (bound-and-true-p abbrev-mode) + (derived-mode-p 'text-mode 'org-mode)) + (ss-crm-install-abbrevs))))) + +(defun ss-crm-capf () + "Return a CRM completion-at-point source." + (when (ss-crm-file-available-p) + (let ((end (point))) + (save-excursion + (skip-syntax-backward "w_") + (let ((beg (point))) + (when (< beg end) + (list beg end #'ss-crm-completion-table + :exclusive 'no + :annotation-function #'ss-crm-marginalia-annotator + :company-docsig + (lambda (candidate) + (when-let ((entry (ss-crm-entry-by-name candidate))) + (ss-crm-summary entry)))))))))) + +(defun ss-crm-enable-capf () + "Install `ss-crm-capf' once in writing buffers." + (unless (or (ss-crm-source-buffer-p) + (memq #'ss-crm-capf completion-at-point-functions)) + (add-hook 'completion-at-point-functions #'ss-crm-capf nil t))) + +(defun ss-crm-track-buffer () + "Refresh CRM caches when `ss-crm-file' is saved." + (when (ss-crm-source-buffer-p) + (add-hook 'after-save-hook #'ss-crm-reload nil t))) + +(defun ss-crm-overview-buffer () + "Reset the CRM buffer to overview when visiting it directly." + (when (ss-crm-source-buffer-p) + (widen) + (goto-char (point-min)) + (org-overview) + (org-cycle-hide-drawers 'all))) + +(defun ss-crm-report-buffer (title property) + "Render a CRM report titled TITLE grouped by PROPERTY." + (let ((groups + (seq-group-by + (lambda (entry) + (let ((value (ss-crm-entry-get entry property))) + (if (string-empty-p (or value "")) + "(none)" + value))) + (ss-crm-entries)))) + (setq groups + (sort groups + (lambda (left right) + (string< (car left) (car right))))) + (with-current-buffer (get-buffer-create "*CRM Report*") + (let ((inhibit-read-only t)) + (erase-buffer) + (org-mode) + (insert "#+title: " title "\n\n") + (dolist (group groups) + (insert "* " (car group) "\n") + (dolist (entry (sort (copy-sequence (cdr group)) + (lambda (left right) + (string< (ss-crm-entry-get left :name) + (ss-crm-entry-get right :name))))) + (insert "- " (ss-crm-display entry) "\n"))) + (goto-char (point-min)) + (read-only-mode 1) + (view-mode 1)) + (pop-to-buffer (current-buffer))))) + +(defun ss-crm-report-by-role () + "Show CRM cards grouped by role." + (interactive) + (ss-crm-report-buffer "CRM by role" :role)) + +(defun ss-crm-report-by-team () + "Show CRM cards grouped by team." + (interactive) + (ss-crm-report-buffer "CRM by team" :team)) + +(defun ss-crm-report-by-manager () + "Show CRM cards grouped by manager." + (interactive) + (ss-crm-report-buffer "CRM by manager" :manager)) + +(defun ss-crm-report-by-relationship () + "Show CRM cards grouped by relationship." + (interactive) + (ss-crm-report-buffer "CRM by relationship" :relationship)) + +(defun ss-crm-report-by-engagement () + "Show CRM cards grouped by engagement." + (interactive) + (ss-crm-report-buffer "CRM by engagement" :engagement)) + +(defun ss-crm-report-by-supplier () + "Show CRM cards grouped by supplier." + (interactive) + (ss-crm-report-buffer "CRM by supplier" :supplier)) + +(defun ss-crm-report-by-location () + "Show CRM cards grouped by location." + (interactive) + (ss-crm-report-buffer "CRM by location" :location)) + +(dolist (hook '(text-mode-hook org-mode-hook)) + (add-hook hook #'ss-crm-enable-capf) + (add-hook hook #'ss-crm-install-abbrevs)) + +(add-hook 'find-file-hook #'ss-crm-track-buffer) +(add-hook 'find-file-hook #'ss-crm-overview-buffer) + +;; -------------------------------------------------- ;; Org ;; -------------------------------------------------- |
