jhpyle / docassemble

A free, open-source expert system for guided interviews and document assembly, based on Python, YAML, and Markdown.
https://docassemble.org
MIT License
782 stars 254 forks source link

Usage of question keyword <sets> #199

Closed abunet closed 2 years ago

abunet commented 5 years ago

HI,

Looking at the examples, it seems that a variable specified with sets is only used like an "anchor" for a question, and not a real variable. For example: `

question: | this is my question 1 fields:

Could you clarify the usage? Thanks

jhpyle commented 5 years ago

Yes, sets only applies to the block as a whole. (It is used on code blocks as well as question blocks.) It cannot be used on specific fields.

All that sets does is advertise that a block may define a variable; whether the block actually defines the variable depends on what the block does. Usually you don't need to use sets, because docassemble can figure out what variables are set by a block. For example, it scans the syntax of a code block, looking for the use of the assignment operator =, and figures out the variables that might be set based on what is on the left hand side of the operator.

You might want to look at the continue button field feature if you want to set a true/false variable after the user clicks Continue on a particular field. https://docassemble.org/docs/fields.html#continue%20button%20field

abunet commented 5 years ago

Thanks for the clarify. The problem is that a button field can be used only with questions that use fields. I loose the yesno questions, signature, review questions and so on...

I've the following workflow in a multiuser environment. A technician starts an interview and fills some data, then he makes a sign. at this point, a mail is sent to the project manager and the technician must wait for the review and sign of the project manager. When the project manager receives the notification he sign in. He must review the questions, makes a sign and then a pdf is produced with the data of the interview and the two signs. I wouldn't like use roles, if possible.

The problem is that I feel hard (maybe I'm missing something) to "force" a desired workflow. I use the initial code block (that runs when every question is rendered) to check the user that has logged in. I need a way to know if the user has just started or restarted the interview (maybe another user has already completed some questions, but he has just started or restarted the interview), and I don't know how to determine this in the initial block. Actually, code block "auto discovery and fill" of variables present in other questions is very powerfull but sometimes makes difficult to keep the desired workflow. Is there a way to debug how the tracker choose a question or a code block (like the callstack printed when there is an error)?

autofill: false

in this case, docassemble should not search for question or other code blocks to fill the variables. This should help write mandatory code that is not "influenced" by other questions (it's all demanded to the developer)


the bookmark could do the same things that currently does, and the variable could set a real boolean value
abunet commented 5 years ago

To explain better my doubts, below an example (simplified):

When the second user (project manager) logs in, technician_user attributes are overwritten with second user information (Michael Rabbit) Does it mean that the mandatory code block is whole re-executed? technician_user wasn't already gathered?

The same for the above code. If I put: if not email_sent_project_manager_ok: insted of: if not defined('email_sent_project_manager_ok'): the email is sent multiple times.

In the signature question I must use a variable. If I use directly _project_manageruser.signature I receive an error.

question: |
  ${ project_manager_user }, please sign the document
signature: project_manager_user_signature
under: |
  ${ project_manager_user } 
mandatory: True

Interview:

metadata:
  title: Plant Testing
---
modules:
  - .common
---
objects:
  - technician_user: Individual
  - technician_user.signature: DAFile
  - project_manager_user: Individual
  - project_manager_user.signature: DAFile
---
id: event email interested people
event: send_mail_to_interested_people
code: |
  email_sent_project_manager_ok = send_email(to=project_manager_user, template=notification_project_manager)
  wait_other_users
---
initial: true
code: |
  if not user_logged_in():
    kick_out_user
---
id: main
code: |
  multi_user = True

  technician_user = Individual()
  technician_user.name.first = user_info().first_name
  technician_user.name.last = user_info().last_name
  technician_user.email = user_info().email

  technician_user.gathered = True

  project_manager_user = Individual()
  project_manager_user.name.first = 'Michael'
  project_manager_user.name.last = 'Rabbit'
  project_manager_user.email = 'michael.rabbit@docassemble.com'

  user_filled_data

  technician_user.signature = technician_user_signature

  if not defined('email_sent_project_manager_ok'):
      send_mail_to_interested_people
  else:
    if not email_sent_project_manager_ok:
      send_mail_to_interested_people

  user_reviewed_data

  project_manager_user.signature = project_manager_user_signature

  user_confirms_document

mandatory: True
---
id: step ask date
question: |
  When have you done the work?
fields:
  - Date: data_doc_date
    datatype: date
    required: True
  - subject: data_subject
    required: True
continue button field: user_filled_data
mandatory: True
---
id: step ask sign technician
question: |
  ${ technician_user }, please sign the document
signature: technician_user_signature
under: |
  ${ technician_user }
mandatory: True
---
id: step project manager review data
field: user_reviewed_data
question: |
  ${ technician_user }, 
  ${ data_doc_date } ${ data_doc_date } 
subquestion: |
  now you'll be asked to sign the document
mandatory: True
---
id: step ask sign project manager
question: |
  ${ project_manager_user }, please sign the document
signature: project_manager_user_signature
under: |
  ${ project_manager_user } 
mandatory: True
---
id: step ask confirm document
question: |
  ${ project_manager_user } 
  ${ project_manager_user.signature } 
  Confirm?
yesno: user_confirms_document
mandatory: True
---
code: |
  if user_confirms_document:
    info_sent = True
  else:
    info_sent = False
---
id: step thanks
mandatory: True
need: 
  - info_sent
question: |
  % if info_sent:
  Thanks
  % else:
  Interview cancelled
  % endif

subquestion: |
  % if info_sent:
  The interview is finished
  % else:
  Interview cancelled
  % endif
buttons:
  Restart: restart
---
id: event kick_out_user
event: kick_out_user
question: |
  Sorry, but you need to login
buttons:
  - Log in: signin
  - Exit: exit
---
id: event wait for other users
event: wait_other_users
question: |
  Please ask ${ project_manager_user } to sign following this
  [link](${ interview_url(temporary=0) })

  After that ${ project_manager_user } has signed,
  come back
  (using
  [the same link](${ interview_url(temporary=0) }))

  Press **Check** to verify if you can continue.
buttons:
  - Verify: refresh
---
id: template notification project manager
template: notification_project_manager
subject: |
  Need Help
content: |
  Hi, ${ project_manager_user },

  ${ technician_user } needs your help

  Fly at this [link](${ interview_url(temporary=0) })

  Good job!
---

I hope I explained myself correctly. Thanks!

jhpyle commented 5 years ago

See https://docassemble.org/docs/logic.html#howitworks

Every time the screen loads, the system will go through the YAML file and run every mandatory block that has not already been run to completion. So if you have a mandatory block that requires asking questions, that mandatory block will be run multiple times. There is no reason to repeatedly re-run the code that defines multi_user or technician_user.name.first. So you should put that preliminary code into its own mandatory code block. Then that code block will run to completion when the interview first starts, and it will be marked as completed, so it will not be run again.

id: main1
mandatory: True
code: |
  multi_user = True

  technician_user = Individual()
  technician_user.name.first = user_info().first_name
  technician_user.name.last = user_info().last_name
  technician_user.email = user_info().email
  project_manager_user = Individual()
  project_manager_user.name.first = 'Michael'
  project_manager_user.name.last = 'Rabbit'
  project_manager_user.email = 'michael.rabbit@docassemble.com'
---
id: main2
mandatory: True
code: |
  user_filled_data
  technician_user.signature

  if task_not_yet_performed('email_sent_project_manager'):
      send_mail_to_interested_people

  user_reviewed_data

  project_manager_user.signature

  user_confirms_document

The best way to let the second user review the information that the first user added is to use a review screen. https://docassemble.org/docs/fields.html#review

Alternatively, you could do something like this:

mandatory: True
code: |
  favorite_fruit
  favorite_vegetable
  favorite_fungus
---
mandatory: True
question: |
  Now, let's review.
field: review_intro
---
mandatory: True
code: |
  if task_not_yet_performed('review'):
    mark_task_as_performed('review')
    force_ask('favorite_fruit', 'favorite_vegetable', 'favorite_fungus')
  final_screen

The force_ask() function will create a list of screens that need to be shown. The system will remember this list and make the user go through the list. But the function should only be called once, or else you'll get into an infinite loop.