From 48dcdc4ed105a8a05f1345c0b00fe8c9bf511670 Mon Sep 17 00:00:00 2001 From: Szymon Szukalski Date: Thu, 9 Apr 2026 10:10:25 +1000 Subject: Extend CRM supplier support --- README.md | 6 ++++-- config.org | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ed1edae..ca2864d 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,8 @@ The people workflow is a CRM rooted at `~/org/areas/people/people.org`. - Failures must surface visibly. The system must not silently fall back to stale state. - The CRM is individual-focused: `TEAM` captures the current working team and `MANAGER` captures the formal organisational manager. - Team structure, org charts, and matrix relationships belong in separate notes, not in the CRM. -- Person cards use `ROLE`, `TEAM`, `MANAGER`, `ENGAGEMENT`, `LOCATION`, and `CURRENT_FOCUS` in that order. +- Person cards use `ROLE`, `TEAM`, `MANAGER`, `ENGAGEMENT`, `SUPPLIER`, `LOCATION`, and `CURRENT_FOCUS` in that order. +- `ENGAGEMENT` and `SUPPLIER` both use fixed lookup lists in `ss/crm-add` so reports stay consistent. - `CURRENT_FOCUS` must stay short and phrase-like so summaries and completion annotations remain readable. Name entry uses fixed abbrevs plus the people CRM: @@ -116,7 +117,7 @@ Name entry uses fixed abbrevs plus the people CRM: - `M-x ss/crm-insert-name` inserts the canonical name at point. - `M-x ss/crm-insert-summary` inserts a compact single-line summary at point. - `M-x ss/crm-add` adds a new person card directly to `~/org/areas/people/people.org`. -- `M-x ss/crm-report-by-role`, `M-x ss/crm-report-by-team`, `M-x ss/crm-report-by-manager`, `M-x ss/crm-report-by-engagement`, and `M-x ss/crm-report-by-location` render grouped Org reports in a read-only buffer. +- `M-x ss/crm-report-by-role`, `M-x ss/crm-report-by-team`, `M-x ss/crm-report-by-manager`, `M-x ss/crm-report-by-engagement`, `M-x ss/crm-report-by-supplier`, and `M-x ss/crm-report-by-location` render grouped Org reports in a read-only buffer. ### Persistent abbrevs @@ -222,6 +223,7 @@ Denote handles long-lived notes. The main bindings are: - `C-c n p` to open `~/org/areas/people/people.org`. - `C-c n P` to add a new person card. - `C-c n R` to show people grouped by manager. +- `C-c n S` to show people grouped by supplier. - `C-c n T` to show people grouped by team. Keyword prompts and directory placement are part of the workflow, not an afterthought. The config is set up so structure is created first, then capture writes into it, with folder placement carrying most of the durable type information. diff --git a/config.org b/config.org index cd804b0..b0081c1 100644 --- a/config.org +++ b/config.org @@ -297,8 +297,10 @@ overview mode, direct visits reset back to overview mode, and exiting card view means widening the buffer and restoring that overview. Keep =CURRENT_FOCUS= short and phrase-like so summaries and completion annotations stay readable. Person cards use a flat property model in this order: =ROLE=, =TEAM=, -=MANAGER=, =ENGAGEMENT=, =LOCATION=, and =CURRENT_FOCUS=. =ABBREV= and -=ALIASES= remain optional helpers for lookup and insertion. +=MANAGER=, =ENGAGEMENT=, =SUPPLIER=, =LOCATION=, and =CURRENT_FOCUS=. +=ENGAGEMENT= and =SUPPLIER= both use fixed lookup lists in =ss/crm-add= so +reports stay consistent. =ABBREV= and =ALIASES= remain optional helpers for +lookup and insertion. #+begin_src emacs-lisp (require 'seq) @@ -309,9 +311,17 @@ Person cards use a flat property model in this order: =ROLE=, =TEAM=, (expand-file-name "areas/people/people.org" "~/org/") "Single source of truth for the people CRM.") - (defconst ss/crm-engagement-values - '("permanent" "contractor" "other") - "Allowed engagement values for people cards.") + (defconst ss/crm-engagement-options + '("Perm" "SOW" "SOW Fixed Outcome" "NCS India") + "Canonical engagement values for people cards.") + + (defconst ss/crm-supplier-options + '("Accenture Song" + "INFOSYS TECHNOLOGIES LIMITED" + "MAKK Integrations Pty Ltd" + "NCSI Technologies India Private Limited" + "TECH MAHINDRA LTD") + "Canonical supplier values for people cards.") (defvar ss/crm--cache nil "Cached CRM entries loaded from `ss/crm-file'.") @@ -343,6 +353,10 @@ Person cards use a flat property model in this order: =ROLE=, =TEAM=, "Return the engagement in ENTRY." (plist-get entry :engagement)) + (defun ss/crm--entry-supplier (entry) + "Return the supplier in ENTRY." + (plist-get entry :supplier)) + (defun ss/crm--entry-manager (entry) "Return the manager in ENTRY." (plist-get entry :manager)) @@ -429,6 +443,7 @@ Person cards use a flat property model in this order: =ROLE=, =TEAM=, :team (org-entry-get nil "TEAM") :manager (org-entry-get nil "MANAGER") :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") ) @@ -620,6 +635,35 @@ Person cards use a flat property model in this order: =ROLE=, =TEAM=, "People by engagement" #'ss/crm--entry-engagement)) + (defun ss/crm-report-by-supplier () + "Show non-empty suppliers grouped by supplier." + (interactive) + (let ((groups + (sort (seq-group-by + #'ss/crm--entry-supplier + (seq-filter + (lambda (entry) + (not (string-empty-p (or (ss/crm--entry-supplier entry) "")))) + (ss/crm-entries))) + (lambda (left right) + (string< (car left) (car right)))))) + (with-current-buffer (get-buffer-create "*People Report*") + (let ((inhibit-read-only t)) + (erase-buffer) + (org-mode) + (insert "#+title: People by supplier\n\n") + (dolist (group groups) + (insert "* " (car group) "\n") + (dolist (entry (sort (copy-sequence (cdr group)) + (lambda (left right) + (string< (ss/crm--entry-name left) + (ss/crm--entry-name right))))) + (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 people grouped by role." (interactive) @@ -661,8 +705,9 @@ Person cards use a flat property model in this order: =ROLE=, =TEAM=, (team (ss/crm-read-required-string "Team: ")) (manager (ss/crm-read-required-string "Manager: ")) (engagement (completing-read "Engagement: " - ss/crm-engagement-values nil t nil nil - "permanent")) + ss/crm-engagement-options nil t)) + (supplier (completing-read "Supplier: " + ss/crm-supplier-options nil t)) (location (ss/crm-read-required-string "Location: ")) (current-focus (ss/crm-read-required-string "Current focus: ")) ) @@ -685,6 +730,7 @@ Person cards use a flat property model in this order: =ROLE=, =TEAM=, (ss/crm--property-line "TEAM" team) (ss/crm--property-line "MANAGER" manager) (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" @@ -1005,6 +1051,7 @@ When CREATE is non-nil, create the datetree entry when missing." ("C-c n p" . ss/crm-open) ("C-c n P" . ss/crm-add) ("C-c n R" . ss/crm-report-by-manager) + ("C-c n S" . ss/crm-report-by-supplier) ("C-c n T" . ss/crm-report-by-team)) :config (setq org-directory ss/org-directory -- cgit v1.2.3