docs(m4): add business/vendor memberships and clean planning docs

This commit is contained in:
zouantchaw
2026-02-25 11:58:21 -05:00
parent 67cf5e0e4c
commit b645927429
5 changed files with 135 additions and 117 deletions

View File

@@ -24,3 +24,6 @@
| 2026-02-24 | 0.1.19 | Added customer stakeholder-wheel mapping and future stakeholder extension model to the M4 schema blueprint. | | 2026-02-24 | 0.1.19 | Added customer stakeholder-wheel mapping and future stakeholder extension model to the M4 schema blueprint. |
| 2026-02-25 | 0.1.20 | Added roadmap CSV schema-reconciliation document with stakeholder capability matrix and concrete schema gap analysis. | | 2026-02-25 | 0.1.20 | Added roadmap CSV schema-reconciliation document with stakeholder capability matrix and concrete schema gap analysis. |
| 2026-02-25 | 0.1.21 | Updated target schema blueprint with roadmap-evidence section plus attendance/offense, stakeholder-network, and settlement-table coverage. | | 2026-02-25 | 0.1.21 | Updated target schema blueprint with roadmap-evidence section plus attendance/offense, stakeholder-network, and settlement-table coverage. |
| 2026-02-25 | 0.1.22 | Updated core actor scenarios with explicit business and vendor user partitioning via membership tables. |
| 2026-02-25 | 0.1.23 | Updated schema blueprint and reconciliation docs to add `business_memberships` and `vendor_memberships` as first-class data actors. |
| 2026-02-25 | 0.1.24 | Removed stale `m4-discrepencies.md` document from M4 planning docs cleanup. |

View File

@@ -9,20 +9,38 @@ Owner: Technical Lead
2. `User`: human identity that signs in. 2. `User`: human identity that signs in.
3. `TenantMembership`: user role/context inside one tenant. 3. `TenantMembership`: user role/context inside one tenant.
4. `Business`: client account served by the tenant. 4. `Business`: client account served by the tenant.
5. `Vendor`: supplier account that can fulfill staffing demand. 5. `BusinessMembership`: maps users to a business with role/status (`owner`, `manager`, `approver`, `viewer`).
6. `Workforce/Staff`: worker profile used for assignment and attendance. 6. `Vendor`: supplier account that can fulfill staffing demand.
7. `StakeholderType`: typed category (`buyer`, `operator`, `vendor_partner`, `workforce`, `partner`). 7. `VendorMembership`: maps users to a vendor with role/status (`owner`, `manager`, `scheduler`, `viewer`).
8. `StakeholderProfile`: typed actor record inside a tenant. 8. `Workforce/Staff`: worker profile used for assignment and attendance.
9. `StakeholderLink`: relationship between stakeholder profiles. 9. `StakeholderType`: typed category (`buyer`, `operator`, `vendor_partner`, `workforce`, `partner`, `procurement_partner`).
10. `StakeholderProfile`: typed actor record inside a tenant.
11. `StakeholderLink`: relationship between stakeholder profiles.
## 1.1 Current schema coverage (today)
Current Data Connect handles this only partially:
1. `Business.userId` supports one primary business user.
2. `Vendor.userId` supports one primary vendor user.
3. `TeamMember` can represent multiple users by team as a workaround.
This is why we need first-class membership tables:
1. `business_memberships`
2. `vendor_memberships`
Without those, client/vendor user partitioning is indirect and harder to enforce safely at scale.
## 2) Minimal actor map ## 2) Minimal actor map
```mermaid ```mermaid
flowchart LR flowchart LR
T["Tenant"] --> TM["TenantMembership"] T["Tenant"] --> TM["TenantMembership (global tenant access)"]
U["User"] --> TM U["User"] --> TM
T --> B["Business"] T --> B["Business"]
T --> V["Vendor"] T --> V["Vendor"]
U --> BM["BusinessMembership"]
BM --> B
U --> VM["VendorMembership"]
VM --> V
U --> S["Workforce/Staff"] U --> S["Workforce/Staff"]
T --> SP["StakeholderProfile"] T --> SP["StakeholderProfile"]
ST["StakeholderType"] --> SP ST["StakeholderType"] --> SP
@@ -40,38 +58,48 @@ Context:
Actor mapping (text): Actor mapping (text):
1. Tenant: `Legendary Event Staffing and Entertainment` (the company using Krow). 1. Tenant: `Legendary Event Staffing and Entertainment` (the company using Krow).
2. User: `Wil` (ops lead), `Maria` (client manager), `Ana` (worker). 2. User: `Wil` (ops lead), `Maria` (Google client manager), `Omar` (Google procurement approver), `Jose` (vendor scheduler), `Ana` (worker).
3. TenantMembership: 3. TenantMembership:
4. `Wil` is `admin` in Legendary tenant. 4. `Wil` is `admin` in Legendary tenant.
5. `Maria` is `manager` in Legendary tenant. 5. `Maria` is `member` in Legendary tenant.
6. `Ana` is `member` in Legendary tenant. 6. `Omar` is `member` in Legendary tenant.
7. Business: `Google Mountain View Cafes` (client account under the tenant). 7. `Jose` is `member` in Legendary tenant.
8. Vendor: `Legendary Staffing Pool A` (or an external approved vendor). 8. `Ana` is `member` in Legendary tenant.
9. Workforce/Staff: `Ana` is a staff profile in workforce, linked to certifications and assignments. 9. BusinessMembership:
10. StakeholderType: `buyer`, `operator`, `vendor_partner`, `workforce`, `procurement_partner`. 10. `Maria` is `manager` in `Google Mountain View Cafes`.
11. StakeholderProfile: 11. `Omar` is `approver` in `Google Mountain View Cafes`.
12. `Google Procurement` = `buyer`. 12. VendorMembership:
13. `Legendary Ops` = `operator`. 13. `Jose` is `scheduler` in `Legendary Staffing Pool A`.
14. `FoodBuy` = `procurement_partner`. 14. `Wil` is `owner` in `Legendary Staffing Pool A`.
15. StakeholderLink: 15. Business: `Google Mountain View Cafes` (client account under the tenant).
16. `Google Procurement` `contracts_with` `Legendary Ops`. 16. Vendor: `Legendary Staffing Pool A` (or an external approved vendor).
17. `Legendary Ops` `sources_from` `Vendor`. 17. Workforce/Staff: `Ana` is a staff profile in workforce, linked to certifications and assignments.
18. `Google Procurement` `reports_through` `FoodBuy`. 18. StakeholderType: `buyer`, `operator`, `vendor_partner`, `workforce`, `procurement_partner`.
19. StakeholderProfile:
20. `Google Procurement` = `buyer`.
21. `Legendary Ops` = `operator`.
22. `FoodBuy` = `procurement_partner`.
23. StakeholderLink:
24. `Google Procurement` `contracts_with` `Legendary Ops`.
25. `Legendary Ops` `sources_from` `Legendary Staffing Pool A`.
26. `Google Procurement` `reports_through` `FoodBuy`.
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant Tenant as "Tenant (Legendary)" participant Tenant as "Tenant (Legendary)"
participant Biz as "Business (Google Cafes)" participant BizUser as "Business user (Maria/Omar)"
participant Ops as "User + TenantMembership (Ops Lead)" participant Ops as "Ops user (Wil)"
participant VendorUser as "Vendor user (Jose)"
participant Staff as "Workforce/Staff (Barista Ana)" participant Staff as "Workforce/Staff (Barista Ana)"
participant StakeRel as "StakeholderLink" participant StakeRel as "StakeholderLink"
Biz->>Ops: "Create staffing request" BizUser->>Ops: "Create staffing request"
Ops->>Tenant: "Create order under tenant scope" Ops->>Tenant: "Create order under tenant scope"
Ops->>StakeRel: "Resolve business-to-vendor/workforce relationships" Ops->>StakeRel: "Resolve business-to-vendor/workforce relationships"
Ops->>Staff: "Assign qualified worker" Ops->>VendorUser: "Dispatch role demand"
VendorUser->>Staff: "Confirm worker assignment"
Staff-->>Ops: "Clock in/out and complete shift" Staff-->>Ops: "Clock in/out and complete shift"
Ops-->>Biz: "Invoice/report generated with audit trail" Ops-->>BizUser: "Invoice/report generated with audit trail"
``` ```
## 4) Scenario B: Another tenant (external vendor-heavy) ## 4) Scenario B: Another tenant (external vendor-heavy)
@@ -82,41 +110,57 @@ Context:
Actor mapping (text): Actor mapping (text):
1. Tenant: `Peakline Events` (another staffing company using Krow). 1. Tenant: `Peakline Events` (another staffing company using Krow).
2. User: `Chris` (operations coordinator), `Nina` (client manager), `Leo` (worker). 2. User: `Chris` (operations coordinator), `Nina` (client manager), `Sam` (vendor manager), `Leo` (worker).
3. TenantMembership: 3. TenantMembership:
4. `Chris` is `admin` in Peakline tenant. 4. `Chris` is `admin` in Peakline tenant.
5. `Nina` is `manager` in Peakline tenant. 5. `Nina` is `member` in Peakline tenant.
6. `Leo` is `member` in Peakline tenant. 6. `Sam` is `member` in Peakline tenant.
7. Business: `NVIDIA Campus Dining` (client account under the tenant). 7. `Leo` is `member` in Peakline tenant.
8. Vendor: `Metro Staffing LLC` (approved external vendor for this tenant). 8. BusinessMembership:
9. Workforce/Staff: `Leo` is a workforce profile fulfilled through Metro Staffing for assignment and attendance. 9. `Nina` is `manager` in `NVIDIA Campus Dining`.
10. StakeholderType: `buyer`, `operator`, `vendor_partner`, `workforce`, `procurement_partner`. 10. VendorMembership:
11. StakeholderProfile: 11. `Sam` is `manager` in `Metro Staffing LLC`.
12. `NVIDIA Procurement` = `buyer`. 12. Business: `NVIDIA Campus Dining` (client account under the tenant).
13. `Peakline Ops` = `operator`. 13. Vendor: `Metro Staffing LLC` (approved external vendor for this tenant).
14. `FoodBuy Regional` = `procurement_partner`. 14. Workforce/Staff: `Leo` is a workforce profile fulfilled through Metro Staffing for assignment and attendance.
15. StakeholderLink: 15. StakeholderType: `buyer`, `operator`, `vendor_partner`, `workforce`, `procurement_partner`.
16. `NVIDIA Procurement` `contracts_with` `Peakline Ops`. 16. StakeholderProfile:
17. `Peakline Ops` `sources_from` `Metro Staffing LLC`. 17. `NVIDIA Procurement` = `buyer`.
18. `NVIDIA Procurement` `reports_through` `FoodBuy Regional`. 18. `Peakline Ops` = `operator`.
19. `FoodBuy Regional` = `procurement_partner`.
20. StakeholderLink:
21. `NVIDIA Procurement` `contracts_with` `Peakline Ops`.
22. `Peakline Ops` `sources_from` `Metro Staffing LLC`.
23. `NVIDIA Procurement` `reports_through` `FoodBuy Regional`.
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant Tenant as "Tenant (Peakline Events)" participant Tenant as "Tenant (Peakline Events)"
participant Biz as "Business (NVIDIA Dining)" participant BizUser as "Business user (Nina)"
participant Ops as "User + TenantMembership (Coordinator)" participant Ops as "Ops user (Chris)"
participant Vendor as "Vendor (Metro Staffing)" participant VendorUser as "Vendor user (Sam)"
participant Staff as "Workforce/Staff (Vendor worker)" participant Staff as "Workforce/Staff (Vendor worker)"
Biz->>Ops: "Request recurring event staffing" BizUser->>Ops: "Request recurring event staffing"
Ops->>Tenant: "Create recurring order" Ops->>Tenant: "Create recurring order"
Ops->>Vendor: "Dispatch required roles" Ops->>VendorUser: "Dispatch required roles"
Vendor->>Staff: "Provide available workers" VendorUser->>Staff: "Provide available workers"
Staff-->>Ops: "Attendance and completion events" Staff-->>Ops: "Attendance and completion events"
Ops-->>Biz: "Settlement + performance reports" Ops-->>BizUser: "Settlement + performance reports"
``` ```
## 5) Why this model scales ## 5) Why this model scales
1. New stakeholders can be added through `StakeholderType` and `StakeholderProfile` without changing core order/shift tables. 1. New stakeholders can be added through `StakeholderType` and `StakeholderProfile` without changing core order/shift tables.
2. Multi-tenant isolation stays strict because all critical records resolve through `Tenant`. 2. Business and vendor user partitioning is explicit through membership tables, not hidden in one owner field.
3. Legendary and non-Legendary operating models both fit the same structure. 3. Multi-tenant isolation stays strict because all critical records resolve through `Tenant`.
4. Legendary and non-Legendary operating models both fit the same structure.
## 6) Industry alignment (primary references)
1. Google Cloud Identity Platform multi-tenancy:
- https://docs.cloud.google.com/identity-platform/docs/multi-tenancy
2. AWS SaaS tenant isolation fundamentals:
- https://docs.aws.amazon.com/whitepapers/latest/saas-architecture-fundamentals/tenant-isolation.html
3. B2B organization-aware login flows (Auth0 Organizations):
- https://auth0.com/docs/manage-users/organizations/login-flows-for-organizations
4. Supplier-side multi-user management patterns (Coupa Supplier Portal):
- https://compass.coupa.com/en-us/products/product-documentation/supplier-resources/for-suppliers/coupa-supplier-portal/set-up-the-csp/users/manage-users

View File

@@ -1,47 +0,0 @@
# M4 Planning Phase: Identified Discrepancies and Enhancements
## Feedback and Discrepancies (Based on M3 Review done by Iliana)
### Mobile Application (Client & Staff)
- **Flexible Shift Locations:** Feedback from the M3 review indicated a need for the ability to specify different locations for individual positions within an order on the mobile app. Currently, the web application handles this by requiring a separate shift for each location.
- **Order Visibility Management:** Currently, when an order is created with multiple positions, the "View Order" page displays them as separate orders. This is due to UI (prototype) cannot support multi-position views in the order. We should consider adopting the "legacy" app's approach to group these positions under a single order for better clarity.
- **Cost Center Clarification:** The purpose and functionality of the "Cost Center" field in the Hub creation process is not clear.
- **Tax Form Data Completeness:** Feedback noted that while tax forms are visible in the Staff mobile application, they appear to be missing critical information. This not clear.
### Web Dashboard
- **Role-Based Content Logic:** The current web dashboard prototype contains some logical inconsistencies regarding user roles:
- **Client Dashboard:** Currently includes Staff Availability, Staff Directory, and Staff Onboarding. Since workers (Staff) are managed by Vendors, these pages should be moved to the Vendor dashboard.
- **Vendor Dashboard:** Currently includes "Teams and Hubs." Since Hubs are client-specific locations where staff clock in/out, these management pages should be moved to the Client dashboard.
- **Admin Dashboard Filtering:** The Admin dashboard requires improved filtering capabilities. Admins should be able to select specific Clients or Vendors to filter related data, such as viewing only the orders associated with a chosen partner.
## Proposed Features and Enhancements (Post-M3 Identification)
- **Feature: Navigation to Hub Details from Coverage Screen (#321)**
- **Description:** Allow users to navigate directly to the Hub Details page by clicking on a hub within the Coverage Screen.
- **Feature: Dedicated Hub Details Screen with Order History (#320)**
- **Description:** Develop a comprehensive Hub Details view that aggregates all hub-specific data, including historical order records.
- **Benefit:** Centralizes information for better decision-making and easier access to historical data.
- **Feature: Dedicated Order Details Screen**
- **Description:** Transition from displaying all order information on the primary "View Order" page to a dedicated "Order Details" screen. This screen will support viewing multiple positions within a single order.
- **Benefit:**
- **Improved UX:** Reduces complexity by grouping associated positions together and presenting them in a structured way.
- **Performance:** Optimizes data loading by fetching detailed position information only when requested.
- **Feature: Optimized Clock-In Page (#350)**
- **Description:** Remove the calendar component from the Clock-In page. Since workers only clock in for current-day assignments, the calendar is unnecessary.
- **Benefit:** Simplifies the interface and reduces user confusion.
- **Feature: Contextual Shift Actions**
- **Description:** Restrict the Clock-In page to show only active or upcoming shifts (starting within 30 minutes). Shift-specific actions (Clock-In/Clock-Out) should be performed within the specific Shift Details page.
- **Reasoning:** This solves issues where staff cannot clock out of overnight shifts (shifts starting one day and ending the next) due to the current day-based UI.
- **Feature: Dedicated Emergency Contact Management (#356)**
- **Description:** Replace the inline form in the "View Emergency Contact" page with a dedicated "Create Emergency Contact" screen.
- **Benefit:** Standardizes the data entry process and improves UI organization within the Staff app.

View File

@@ -71,13 +71,15 @@ What already exists and is useful:
Current structural gaps for roadmap scale: Current structural gaps for roadmap scale:
1. No tenant boundary key on core tables (`tenant_id` missing). 1. No tenant boundary key on core tables (`tenant_id` missing).
2. No first-class stakeholder profile/link model for buyer/operator/partner/sector relationships. 2. No first-class business user partitioning table (`business_memberships` missing).
3. Attendance history is not first-class (check in/out only inside `applications`). 3. No first-class vendor user partitioning table (`vendor_memberships` missing).
4. No offense policy, offense event, or enforcement action tables. 4. No first-class stakeholder profile/link model for buyer/operator/partner/sector relationships.
5. Finance is coarse (invoice + recent payment), missing line items, payment runs, remittance artifact model. 5. Attendance history is not first-class (check in/out only inside `applications`).
6. Sensitive bank fields are currently modeled directly in `accounts` (`accountNumber`, `routeNumber`). 6. No offense policy, offense event, or enforcement action tables.
7. Many core workflow fields are JSON (`orders.assignedStaff`, `orders.shifts`, `shift.managers`, `assignment.managers`). 7. Finance is coarse (invoice + recent payment), missing line items, payment runs, remittance artifact model.
8. Money still uses float in critical tables. 8. Sensitive bank fields are currently modeled directly in `accounts` (`accountNumber`, `routeNumber`).
9. Many core workflow fields are JSON (`orders.assignedStaff`, `orders.shifts`, `shift.managers`, `assignment.managers`).
10. Money still uses float in critical tables.
Connector boundary gap: Connector boundary gap:
1. 147 Data Connect mutation operations exist. 1. 147 Data Connect mutation operations exist.
@@ -88,10 +90,12 @@ Connector boundary gap:
### 6.1 Tenant and stakeholder graph ### 6.1 Tenant and stakeholder graph
1. `tenants` 1. `tenants`
2. `tenant_memberships` 2. `tenant_memberships`
3. `stakeholder_types` 3. `business_memberships`
4. `stakeholder_profiles` 4. `vendor_memberships`
5. `stakeholder_links` 5. `stakeholder_types`
6. `role_bindings` (scoped to tenant/team/hub/business/vendor/resource) 6. `stakeholder_profiles`
7. `stakeholder_links`
8. `role_bindings` (scoped to tenant/team/hub/business/vendor/resource)
### 6.2 Attendance and timesheet reliability ### 6.2 Attendance and timesheet reliability
1. `attendance_events` (append-only clock-in/out/NFC/manual-corrected) 1. `attendance_events` (append-only clock-in/out/NFC/manual-corrected)
@@ -124,6 +128,10 @@ Connector boundary gap:
```mermaid ```mermaid
erDiagram erDiagram
TENANT ||--o{ TENANT_MEMBERSHIP : has TENANT ||--o{ TENANT_MEMBERSHIP : has
BUSINESS ||--o{ BUSINESS_MEMBERSHIP : has
VENDOR ||--o{ VENDOR_MEMBERSHIP : has
USER ||--o{ BUSINESS_MEMBERSHIP : belongs_to
USER ||--o{ VENDOR_MEMBERSHIP : belongs_to
TENANT ||--o{ STAKEHOLDER_PROFILE : has TENANT ||--o{ STAKEHOLDER_PROFILE : has
STAKEHOLDER_PROFILE ||--o{ STAKEHOLDER_LINK : links_to STAKEHOLDER_PROFILE ||--o{ STAKEHOLDER_LINK : links_to

View File

@@ -25,6 +25,7 @@ Practical meaning:
3. Not every record starts as a full active user: 3. Not every record starts as a full active user:
- invite-first or pending onboarding records are valid, - invite-first or pending onboarding records are valid,
- then bound to `user_id` when activation is completed. - then bound to `user_id` when activation is completed.
4. Business-side users and vendor-side users are partitioned with dedicated membership tables, not only one `userId` owner field.
```mermaid ```mermaid
flowchart LR flowchart LR
@@ -41,16 +42,16 @@ The stakeholder labels from the customer workshop map to schema as follows:
1. Buyer (Procurements): 1. Buyer (Procurements):
- Buyer users inside a business/client account. - Buyer users inside a business/client account.
- Schema anchor: `users` + `tenant_memberships` + `team_members` (procurement team scope). - Schema anchor: `users` + `tenant_memberships` + `business_memberships`.
2. Enterprises (Operator): 2. Enterprises (Operator):
- Tenant operator/admin users running staffing operations. - Tenant operator/admin users running staffing operations.
- Schema anchor: `tenants`, `team_members`, command-side permissions. - Schema anchor: `tenants`, `tenant_memberships`, `role_bindings`.
3. Sectors (Execution): 3. Sectors (Execution):
- Operational segments or business units executing events. - Operational segments or business units executing events.
- Schema anchor: `teams`, `team_hubs`, `team_hud_departments`, `roles`. - Schema anchor: `teams`, `team_hubs`, `team_hud_departments`, `roles`.
4. Approved Vendor: 4. Approved Vendor:
- Supplier companies approved to fulfill staffing demand. - Supplier companies approved to fulfill staffing demand.
- Schema anchor: `vendors`, `workforce`, `vendor_rates`, `vendor_benefit_plans`. - Schema anchor: `vendors`, `vendor_memberships`, `workforce`, `vendor_rates`, `vendor_benefit_plans`.
5. Workforce: 5. Workforce:
- Individual workers/staff and their assignments. - Individual workers/staff and their assignments.
- Schema anchor: `staffs`, `staff_roles`, `applications`, `assignments`, `certificates`, `staff_documents`. - Schema anchor: `staffs`, `staff_roles`, `applications`, `assignments`, `certificates`, `staff_documents`.
@@ -99,15 +100,19 @@ What the exports confirmed:
Tables: Tables:
1. `users` (source identity, profile, auth linkage) 1. `users` (source identity, profile, auth linkage)
2. `tenant_memberships` (new; membership + base access per tenant) 2. `tenant_memberships` (new; membership + base access per tenant)
3. `team_members` (membership + scope per team) 3. `business_memberships` (new; user access to business account scope)
4. `roles` (new) 4. `vendor_memberships` (new; user access to vendor account scope)
5. `permissions` (new) 5. `team_members` (membership + scope per team)
6. `role_bindings` (new; who has which role in which scope) 6. `roles` (new)
7. `permissions` (new)
8. `role_bindings` (new; who has which role in which scope)
Rules: Rules:
1. Unique tenant membership: `(tenant_id, user_id)`. 1. Unique tenant membership: `(tenant_id, user_id)`.
2. Unique team membership: `(team_id, user_id)`. 2. Unique business membership: `(business_id, user_id)`.
3. Access checks resolve through tenant membership first, then optional team/hub scope. 3. Unique vendor membership: `(vendor_id, user_id)`.
4. Unique team membership: `(team_id, user_id)`.
5. Access checks resolve through tenant membership first, then business/vendor/team scope.
## 4.2 Organization and Tenant ## 4.2 Organization and Tenant
Tables: Tables:
@@ -121,6 +126,7 @@ Tables:
Rules: Rules:
1. Every command-critical row references `tenant_id`. 1. Every command-critical row references `tenant_id`.
2. All list queries must include tenant predicate. 2. All list queries must include tenant predicate.
3. Business and vendor routes must enforce membership scope before data access.
## 4.8 RBAC rollout strategy (deferred enforcement) ## 4.8 RBAC rollout strategy (deferred enforcement)
RBAC should be introduced in phases and **not enforced everywhere immediately**. RBAC should be introduced in phases and **not enforced everywhere immediately**.
@@ -250,6 +256,10 @@ erDiagram
TENANT ||--o{ TEAM : owns TENANT ||--o{ TEAM : owns
TEAM ||--o{ TEAM_MEMBER : has TEAM ||--o{ TEAM_MEMBER : has
USER ||--o{ TEAM_MEMBER : belongs_to USER ||--o{ TEAM_MEMBER : belongs_to
USER ||--o{ BUSINESS_MEMBERSHIP : belongs_to
USER ||--o{ VENDOR_MEMBERSHIP : belongs_to
BUSINESS ||--o{ BUSINESS_MEMBERSHIP : has
VENDOR ||--o{ VENDOR_MEMBERSHIP : has
BUSINESS ||--o{ ORDER : requests BUSINESS ||--o{ ORDER : requests
VENDOR ||--o{ ORDER : fulfills VENDOR ||--o{ ORDER : fulfills