I've wanted the exact same "I did this yesterday" behavior for a while and never got around to trying to implement it. But now if I can get points for it ....
This behavior seems to be hard-coded into
org-todo. The line in
org.el that sets the CLOSED timestamp is
(org-add-planning-info 'closed (org-current-effective-time)) and the LOGBOOK notes are added by
org-add-log-setup, which in turn calls
org-effective-current-time does what it sounds like and returns the effective time.
The obvious solution is to temporarily change
org-effective-current-time to something that prompts for a date. But then we get prompted for the date multiple times with every call, which is annoying. I don't know a good way to avoid it, but you can just save off the user inputed value and keep that around until the end of the function.
This code seems to work and only prompts once when a state change would be logged.
(defun org-todo-with-date (&optional arg)
(cl-letf* ((org-read-date-prefer-future nil)
(my-current-time (org-read-date t t nil "when:" nil nil nil))
#'(lambda () my-current-time)))