Before a user can create or view a Financial Document, a permission check is performed. As a sample we will have a closer look what is checks are performed when a user wants to create a new quote.
Permission checks creation of a new quote: /intranet-invoices/new?cost_type_id=3702
1) Depending on the required permission type (read/write) a list of cost_type_ids is generated:
set create_cost_types [im_cost_type_write_permissions $user_id]
“im_cost_type_write_permissions” performs the following checks:
set can_write [expr [im_permission $user_id add_costs] || [im_permission $user_id add_invoices]]
if {!$can_write} { return [list] }
set result [db_list writable_cost_centers "
select distinct
ct.cost_type_id
from
im_cost_centers cc,
im_cost_types ct,
acs_permissions p,
party_approved_member_map m,
acs_object_context_index c,
acs_privilege_descendant_map h
where
cc.cost_center_id = c.object_id
and ct.write_privilege = h.descendant
and p.object_id = c.ancestor_id
and m.member_id = :user_id
and p.privilege = h.privilege
and p.grantee_id = m.party_id
"]
return $result
Let’s have a closer look at this sql statement and the tables and views involved.
Table: im_cost_centers
… all Cost Centers …

View: im_cost_types
im_cost_types contains all privileges related to the “Financial Documents”

Table: acs_permissions
Our well known acs_permissions, mapping object_id (1st column), grantee_id (2nd column) and privilege (3rd column).
More information here.

Records in acs_permissions are added/removed setting values in /intranet/admin/profiles/
In case a new privilege is set, a record will be added to this table, if a privilege is removed, the respective record will be removed.
Table: party_approved_member_map
If user is a member of more than one profile, his accumulates the permissions of all profile he’s a member of. User/Profile relationship is managed in table “parties”.
Table “party_approved_member_map” maps a party to the fully expanded list of parties represented by that party including the party itself. So if a party is an individual this view will have exactly one mapping hat is from that party to itself. If a view is a group containing three individuals,this view will have four rows for that party, one for each member,and one from the party to itself. As the table name indicates, only approved members are considered. More information on Open ACS parties here

Table: acs_privilege_descendant_map
.. pretty obvious too, manages the relationship between related parent/child privileges:
| oid | privilege | descendant |

Table: acs_object_context_index
This table manages the inheritance of privileges. See OpenACS Permissions Tediously Explained – by Vadim Nasardinov for further information.
| object_id | ancestor_id | n_generations |

Based from what we have learned about the tables involved we now investigate further the “where” clause:
...
where
cc.cost_center_id = c.object_id
and ct.write_privilege = h.descendant
and p.object_id = c.ancestor_id
and m.member_id = :user_id
and p.privilege = h.privilege
and p.grantee_id = m.party_id
a) If a permission is granted for the parent “Cost Center”, it should also be granted for its childs:
im_cost_centers.cost_center_id = acs_object_context_index.object_id"
b) The same is true for cost types, a privilege granted on a higher level should be inherited to its childs:
im_cost_types.write_privilege = acs_privilege_descendant_map.descendant
c) … and for parties (user):
acs_permissions.object_id = acs_object_context_index.ancestor_id
d) Get (approved ) parties related to user to look up:
party_approved_member_map.member_id = :user_id
e) Include all sub privileges:
acs_permissions.privilege = acs_privilege_descendant_map.privilege
f) Limit acs_permissions to approved members
acs_permissions.grantee_id = party_approved_member_map.party_id
You might want to take some time to digest that …
2) In a second step we check, if the Financial Document to be shown/created is member of this list:
if {[lsearch -exact $create_cost_types $cost_type_id] == -1} {
ad_return_complaint "Insufficient Privileges" "
You don't have sufficient privileges to create a
[db_string t "select im_category_from_id(:cost_type_id)"]."
return
}