Your Nonprofit Runs on 6 Disconnected Tools. Your Donors Notice.
Most nonprofits we audit run a patchwork: Bloomerang for donors, QuickBooks for accounting, Excel for grant budgets, Eventbrite for events, and a Google Sheet for volunteer hours. None of these systems talk to each other. When the board asks "how much did the spring gala cost vs. what it raised?" — the answer takes three days to assemble.
Odoo 19 changes this. Its modular architecture lets you build a unified nonprofit platform — donor CRM, donation processing, grant lifecycle management, restricted fund accounting, volunteer tracking, and campaign management — in a single database. When a donor gives $5,000 at your gala, that transaction flows from event registration through the payment processor to the donor record and into the correct restricted fund, automatically.
This guide covers the complete implementation: 7 modules with production-tested code, deployed for food banks, community foundations, and international relief agencies.
Building a Donor CRM in Odoo 19: Contact Segmentation, Giving History, and Engagement Scoring
Odoo's CRM module was built for sales pipelines, but with the right configuration it becomes a powerful donor management system. The key insight: a donor is a partner with giving history, not a lead with a close date. We extend res.partner with nonprofit-specific fields rather than creating a standalone donor model — this preserves compatibility with invoicing, email marketing, portal access, and every other Odoo module that relies on partners.
from odoo import api, fields, models
from dateutil.relativedelta import relativedelta
class ResPartner(models.Model):
_inherit = 'res.partner'
is_donor = fields.Boolean(string='Is Donor', default=False)
donor_type = fields.Selection([
('individual', 'Individual'),
('corporate', 'Corporate'),
('foundation', 'Foundation'),
('government', 'Government Agency'),
('anonymous', 'Anonymous'),
], string='Donor Type', tracking=True)
donor_since = fields.Date(
string='Donor Since', tracking=True,
)
donor_tier = fields.Selection([
('prospect', 'Prospect'),
('first_time', 'First-Time Donor'),
('recurring', 'Recurring Donor'),
('major', 'Major Donor'),
('legacy', 'Legacy / Planned Giving'),
], string='Donor Tier', compute='_compute_donor_tier',
store=True, tracking=True)
lifetime_giving = fields.Monetary(
string='Lifetime Giving',
compute='_compute_giving_stats',
store=True, currency_field='currency_id',
)
ytd_giving = fields.Monetary(
string='Year-to-Date Giving',
compute='_compute_giving_stats',
store=True, currency_field='currency_id',
)
last_donation_date = fields.Date(
string='Last Donation',
compute='_compute_giving_stats', store=True,
)
donation_count = fields.Integer(
compute='_compute_giving_stats', store=True,
)
engagement_score = fields.Float(
string='Engagement Score',
compute='_compute_engagement_score',
store=True, help='0-100 RFM score.',
)
tax_receipt_required = fields.Boolean(
string='Tax Receipt Required', default=True,
)
donation_ids = fields.One2many(
'nonprofit.donation', 'donor_id',
string='Donations',
)
@api.depends('donation_ids.amount', 'donation_ids.date')
def _compute_giving_stats(self):
today = fields.Date.today()
year_start = today.replace(month=1, day=1)
for partner in self:
confirmed = partner.donation_ids.filtered(
lambda d: d.state == 'confirmed'
)
partner.lifetime_giving = sum(confirmed.mapped('amount'))
partner.ytd_giving = sum(confirmed.filtered(
lambda d: d.date >= year_start
).mapped('amount'))
partner.donation_count = len(confirmed)
dates = confirmed.mapped('date')
partner.last_donation_date = max(dates) if dates else False
@api.depends('lifetime_giving', 'donation_count')
def _compute_donor_tier(self):
for partner in self:
if partner.lifetime_giving >= 25000:
partner.donor_tier = 'major'
elif partner.donation_count >= 12:
partner.donor_tier = 'recurring'
elif partner.donation_count >= 1:
partner.donor_tier = 'first_time'
else:
partner.donor_tier = 'prospect' The engagement_score uses an RFM model adapted for donor retention. A donor who gave $100 last week scores higher than one who gave $10,000 three years ago. The score drives automated actions: donors below 30 get a re-engagement sequence, donors above 80 get a personal call from the development director.
Some Odoo nonprofits map donor cultivation to CRM pipeline stages (Prospect → Qualified → Solicited → Pledged → Donated). This works for major gift cultivation but breaks down for the 90% of donors who give online without a cultivation cycle. Use CRM pipelines for major gift prospects ($10K+) and the donor_tier computed field for everyone else. Mixing the two creates duplicate records and conflicting reporting.
Online and Offline Donation Processing in Odoo 19 with Automatic Tax Receipts
Donations arrive through multiple channels: online, in-person at events, by mail (checks still account for 20% of nonprofit revenue), and recurring plans. A unified donation model handles all of them — creating accounting entries, issuing tax receipts, and updating donor records automatically.
class NonprofitDonation(models.Model):
_name = 'nonprofit.donation'
_description = 'Donation Record'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'date desc'
name = fields.Char(string='Reference',
default=lambda self: self.env['ir.sequence'].next_by_code('nonprofit.donation'))
donor_id = fields.Many2one(
'res.partner', string='Donor', required=True,
domain="[('is_donor', '=', True)]", tracking=True)
date = fields.Date(string='Donation Date', required=True,
default=fields.Date.today, tracking=True)
amount = fields.Monetary(string='Amount', required=True,
currency_field='currency_id', tracking=True)
currency_id = fields.Many2one('res.currency',
default=lambda self: self.env.company.currency_id)
channel = fields.Selection([
('online', 'Online / Website'), ('event', 'Event'),
('mail', 'Mail / Check'), ('bank', 'Bank Transfer'),
('in_kind', 'In-Kind'), ('recurring', 'Recurring Plan'),
], string='Channel', required=True, tracking=True)
fund_id = fields.Many2one('nonprofit.fund', string='Designated Fund')
campaign_id = fields.Many2one('utm.campaign', string='Campaign')
analytic_account_id = fields.Many2one(
'account.analytic.account', string='Analytic Account')
state = fields.Selection([
('draft', 'Draft'), ('confirmed', 'Confirmed'),
('receipted', 'Tax Receipt Sent'), ('cancelled', 'Cancelled'),
], default='draft', tracking=True)
move_id = fields.Many2one('account.move', string='Journal Entry', readonly=True)
is_recurring = fields.Boolean(string='Recurring')
def action_confirm(self):
for donation in self:
donation._create_journal_entry()
donation.state = 'confirmed'
if donation.donor_id.tax_receipt_required:
donation._send_tax_receipt()
def _create_journal_entry(self):
journal = self.env['account.journal'].search([('code', '=', 'DON')], limit=1)
move = self.env['account.move'].create({
'journal_id': journal.id,
'date': self.date,
'ref': f"Donation {self.name} - {self.donor_id.display_name}",
'line_ids': [
(0, 0, {
'name': self.name,
'account_id': self.fund_id.income_account_id.id,
'credit': self.amount,
'analytic_distribution': {{
str(self.analytic_account_id.id): 100
}} if self.analytic_account_id else False,
}),
(0, 0, {
'name': self.name,
'account_id': journal.default_account_id.id,
'debit': self.amount,
}),
],
})
move.action_post()
self.move_id = move.idThe journal entry creation is where fund accounting meets donation processing. Each donation automatically posts to the correct income account based on its designated fund, with the analytic account carrying the restriction tag. When a donor gives $1,000 to your "Building Fund," Odoo creates a journal entry crediting the restricted revenue account and tagging the analytic distribution so the funds are tracked separately from unrestricted operating revenue.
| Channel | Payment Method | Auto-Confirm | Tax Receipt |
|---|---|---|---|
| Online | Stripe / PayPal | Yes (on payment callback) | Immediate email |
| Event | POS / Card reader | Yes | End-of-day batch |
| Mail / Check | Manual entry | No (requires review) | On confirmation |
| Recurring | Stripe subscription | Yes (webhook) | Annual summary (Jan) |
Odoo 19's Subscription module handles recurring billing natively. Instead of building custom cron jobs for recurring donations, create a subscription product called "Monthly Donation" with a configurable price. The subscriber gets charged automatically, and the subscription engine handles failed payments, retries, and cancellation — all logged on the donor's partner record.
Grant Lifecycle Management in Odoo 19: From Application Through Compliance Reporting
Grant management is where most nonprofits' systems fail. A typical community foundation manages 30-50 active grants, each with its own budget, reporting schedule, and compliance requirements. Tracking this in spreadsheets works until a missed reporting deadline jeopardizes future funding.
class NonprofitGrant(models.Model):
_name = 'nonprofit.grant'
_description = 'Grant / Award'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'date_end asc'
name = fields.Char(string='Grant Title', required=True, tracking=True)
funder_id = fields.Many2one(
'res.partner', string='Funder / Grantor', required=True, tracking=True)
reference = fields.Char(string='Grant Reference #')
amount_awarded = fields.Monetary(
string='Amount Awarded', required=True,
currency_field='currency_id', tracking=True)
amount_spent = fields.Monetary(
string='Amount Spent',
compute='_compute_spent', store=True,
)
amount_remaining = fields.Monetary(
string='Remaining Budget',
compute='_compute_spent', store=True,
)
burn_rate = fields.Float(
string='Monthly Burn Rate',
compute='_compute_spent', store=True,
)
currency_id = fields.Many2one('res.currency',
default=lambda self: self.env.company.currency_id)
date_start = fields.Date(string='Grant Period Start', required=True)
date_end = fields.Date(string='Grant Period End', required=True)
analytic_account_id = fields.Many2one(
'account.analytic.account', string='Analytic Account',
required=True, help='All grant expenses flow through this account.')
state = fields.Selection([
('draft', 'Application'), ('submitted', 'Submitted'),
('awarded', 'Awarded'), ('active', 'Active'),
('reporting', 'Final Reporting'), ('closed', 'Closed'),
('rejected', 'Rejected'),
], default='draft', tracking=True)
program_id = fields.Many2one('nonprofit.program', string='Program')
eligible_expense_categ_ids = fields.Many2many(
'product.category', string='Eligible Expense Categories',
help='Only these categories may be charged to this grant.',
)
report_ids = fields.One2many(
'nonprofit.grant.report', 'grant_id', string='Reports',
)
next_report_date = fields.Date(
string='Next Report Due', compute='_compute_next_report', store=True,
)
reporting_frequency = fields.Selection([
('monthly', 'Monthly'), ('quarterly', 'Quarterly'),
('semi_annual', 'Semi-Annual'), ('annual', 'Annual'),
('final', 'Final Only'),
], string='Reporting Frequency', default='quarterly')
@api.depends('analytic_account_id')
def _compute_spent(self):
for grant in self:
if not grant.analytic_account_id:
grant.amount_spent = 0
grant.amount_remaining = grant.amount_awarded
grant.burn_rate = 0
continue
lines = self.env['account.analytic.line'].search([
('account_id', '=',
grant.analytic_account_id.id),
('amount', '<', 0),
])
spent = abs(sum(lines.mapped('amount')))
grant.amount_spent = spent
grant.amount_remaining = (
grant.amount_awarded - spent
)
months_elapsed = max(1, (
fields.Date.today() - grant.date_start
).days / 30.44)
grant.burn_rate = spent / months_elapsed
def action_check_overspend(self):
"""Alert if grant is approaching budget limit."""
for grant in self:
pct = (grant.amount_spent /
grant.amount_awarded * 100
if grant.amount_awarded else 0)
if pct >= 90:
grant.activity_schedule(
'mail.mail_activity_data_warning',
summary=f'Grant {grant.name} is at '
f'{pct:.0f}% spend',
note=f'Remaining: '
f'{grant.amount_remaining:,.2f}',
) The eligible_expense_categ_ids field prevents the most common grant compliance violation: charging ineligible expenses to a restricted grant. When a PO or expense report hits a grant's analytic account, a constraint check ensures the product category is eligible. Travel expenses against a supplies-only grant get blocked before reaching the books.
class AccountMoveLine(models.Model):
_inherit = 'account.move.line'
@api.constrains('analytic_distribution')
def _check_grant_eligible_expenses(self):
"""Block ineligible expenses on grant accounts."""
for line in self:
if not line.analytic_distribution:
continue
for acct_id, pct in line.analytic_distribution.items():
grant = self.env['nonprofit.grant'].search([
('analytic_account_id', '=', int(acct_id)),
('state', '=', 'active'),
], limit=1)
if not grant or not grant.eligible_expense_categ_ids:
continue
categ = line.product_id.categ_id if line.product_id else False
if categ and categ not in grant.eligible_expense_categ_ids:
raise ValidationError(
f'Category "{categ.display_name}" is not '
f'eligible for grant "{grant.name}".'
)Each grant tracks its own reporting schedule. Quarterly-reporting grants automatically create activities 14 days before each due date. The report template pulls actual vs. budget from the analytic account — no manual data gathering.
Fund Accounting with Analytic Accounts: Tracking Restricted, Temporarily Restricted, and Unrestricted Funds
Fund accounting is the fundamental difference between nonprofit and for-profit bookkeeping. A nonprofit has dozens of money pools — restricted by donor intent, grant terms, or board designation. FASB ASC 958 and your auditor require separate tracking and reporting. Odoo 19's analytic accounting engine makes this possible without parallel books.
class NonprofitFund(models.Model):
_name = 'nonprofit.fund'
_description = 'Nonprofit Fund'
_inherit = ['mail.thread']
name = fields.Char(string='Fund Name', required=True, tracking=True)
code = fields.Char(string='Fund Code', required=True)
fund_type = fields.Selection([
('unrestricted', 'Unrestricted'),
('temp_restricted', 'Temporarily Restricted'),
('perm_restricted', 'Permanently Restricted'),
('board_designated', 'Board Designated'),
], string='Fund Type', required=True, tracking=True)
analytic_plan_id = fields.Many2one(
'account.analytic.plan', string='Analytic Plan', required=True,
)
analytic_account_id = fields.Many2one(
'account.analytic.account', string='Analytic Account', required=True,
)
income_account_id = fields.Many2one(
'account.account', string='Revenue Account', required=True,
help='E.g., 4100 Restricted Revenue.',
)
expense_account_id = fields.Many2one(
'account.account', string='Expense Account',
)
balance = fields.Monetary(
string='Current Balance', compute='_compute_balance',
store=True, currency_field='currency_id',
)
currency_id = fields.Many2one('res.currency',
default=lambda self: self.env.company.currency_id)
restriction_description = fields.Text(
string='Restriction Details',
)
active = fields.Boolean(default=True)
@api.depends('analytic_account_id')
def _compute_balance(self):
for fund in self:
if not fund.analytic_account_id:
fund.balance = 0
continue
lines = self.env['account.analytic.line'].search([
('account_id', '=',
fund.analytic_account_id.id),
])
fund.balance = sum(lines.mapped('amount'))| Fund Type | Analytic Plan | Revenue Account | Example |
|---|---|---|---|
| Unrestricted | General Operations | 4000 - Unrestricted Revenue | Annual appeal donations |
| Temporarily Restricted | Programs | 4100 - Restricted Revenue | Youth program grant (expires 2027) |
| Permanently Restricted | Endowment | 4200 - Endowment Revenue | Smith Family Endowment (principal locked) |
| Board Designated | Reserves | 4000 - Unrestricted Revenue | Building reserve (internally restricted) |
The critical detail: Odoo 19's multi-plan analytic distribution lets you tag a single journal entry with multiple dimensions. A coordinator's salary can be 60% "Youth Mentoring" grant, 30% "General Operations," and 10% "Summer Camp." The payroll entry captures this split, and fund reporting stays accurate without manual allocations.
Your chart of accounts should separate revenue and expenses by natural classification (salaries, rent, supplies), while analytic accounts handle the functional classification (program vs. management vs. fundraising). This gives you the two-dimensional reporting FASB ASC 958 requires: the Statement of Functional Expenses shows natural classification across columns and functional classification down rows. Odoo's analytic engine generates both dimensions from a single set of journal entries.
Volunteer Management in Odoo 19: Scheduling, Hour Tracking, and Skill Matching
Volunteers are the unpaid workforce powering most nonprofits — and the hardest to manage. The #1 reason volunteers leave is feeling disorganized or underutilized. A structured system matches skills to needs, tracks hours for grant reporting, and keeps volunteers engaged.
class NonprofitVolunteer(models.Model):
_name = 'nonprofit.volunteer'
_description = 'Volunteer'
_inherit = ['mail.thread', 'mail.activity.mixin']
partner_id = fields.Many2one(
'res.partner', string='Contact',
required=True, ondelete='cascade',
)
name = fields.Char(
related='partner_id.display_name', store=True,
)
state = fields.Selection([
('prospect', 'Prospect'),
('active', 'Active'),
('on_hold', 'On Hold'),
('inactive', 'Inactive'),
], default='prospect', tracking=True)
skill_ids = fields.Many2many(
'nonprofit.volunteer.skill', string='Skills',
)
availability = fields.Selection([
('weekdays', 'Weekdays'),
('weekends', 'Weekends'),
('evenings', 'Evenings'),
('flexible', 'Flexible'),
], string='General Availability')
background_check = fields.Selection([
('pending', 'Pending'),
('cleared', 'Cleared'),
('failed', 'Failed'),
], string='Background Check', tracking=True)
total_hours = fields.Float(
string='Total Volunteer Hours',
compute='_compute_total_hours', store=True,
)
timesheet_ids = fields.One2many(
'nonprofit.volunteer.timesheet', 'volunteer_id',
string='Hour Log',
)
program_ids = fields.Many2many(
'nonprofit.program', string='Assigned Programs',
)
@api.depends('timesheet_ids.hours')
def _compute_total_hours(self):
for vol in self:
vol.total_hours = sum(vol.timesheet_ids.mapped('hours'))
class VolunteerTimesheet(models.Model):
_name = 'nonprofit.volunteer.timesheet'
_description = 'Volunteer Hour Log'
_order = 'date desc'
volunteer_id = fields.Many2one(
'nonprofit.volunteer', required=True,
ondelete='cascade',
)
date = fields.Date(required=True, default=fields.Date.today)
hours = fields.Float(string='Hours', required=True)
program_id = fields.Many2one('nonprofit.program', string='Program')
grant_id = fields.Many2one(
'nonprofit.grant', string='Grant',
help='If hours count toward grant match.',
)
description = fields.Text(string='Activity Description')
approved = fields.Boolean(default=False) The grant_id field on the volunteer timesheet is critical for in-kind match reporting. Federal grants often require a 1:1 match — volunteer labor valued at the Independent Sector rate ($33.49/hour) counts toward it. When your grant report shows $50,000 in volunteer match, the auditor needs individual time entries with dates, descriptions, and approvals.
Campaign and Event Fundraising in Odoo 19: Galas, Peer-to-Peer, and Year-End Appeals
Fundraising events are where your CRM, donation processing, and email marketing converge. Odoo 19's Events module, combined with the UTM campaign tracker and the donation model we built earlier, handles the full lifecycle. The key: create a UTM campaign for each fundraising initiative so every donation, registration, and email interaction is attributed to a single report.
| Fundraising Type | Odoo Modules | Key Metric | Automation |
|---|---|---|---|
| Annual Gala | Events + POS + Donations | Net revenue per attendee | Post-event thank-you + tax receipt |
| Year-End Appeal | Email Marketing + Donations | Conversion rate by segment | 3-email drip with giving link |
| Peer-to-Peer | Website + Portal + Donations | Avg raised per fundraiser | Fundraiser page + progress bar |
| Monthly Giving | Subscriptions + Donations | Retention rate at 12 months | Failed payment recovery sequence |
For galas, Odoo's POS module handles the live component. Set up terminals at registration and the silent auction. Each "sale" is a donation — the product uses a donation income account, and the receipt shows the tax-deductible amount. At night's end, the POS session close reconciles payments and creates donation records automatically.
The tax-deductible amount of a silent auction item is the winning bid minus the fair market value (FMV) of the item. If someone bids $500 on a vacation package with an FMV of $350, the deductible amount is $150. Store the FMV on the product record and compute the deductible portion in the tax receipt. Getting this wrong is a compliance issue — the IRS requires disclosure of the FMV for any item valued over $75.
Nonprofit Reporting Dashboards: Board Reports, Grant Compliance, and Donor Analytics
Nonprofit reporting serves three audiences: the board wants financial health metrics, grant funders want detailed expense reports, and development staff want donor analytics. Odoo 19's dashboard views serve all three from the same data.
<odoo>
<record id="action_nonprofit_dashboard"
model="ir.actions.act_window">
<field name="name">Nonprofit Dashboard</field>
<field name="res_model">nonprofit.donation</field>
<field name="view_mode">pivot,graph,list</field>
<field name="context">{{
'search_default_this_year': 1,
'group_by': ['fund_id', 'channel'],
'pivot_measures': ['amount'],
'pivot_row_groupby': ['fund_id'],
'pivot_column_groupby': ['date:month'],
}}</field>
</record>
<record id="action_grant_budget_report"
model="ir.actions.act_window">
<field name="name">Grant Budget vs Actual</field>
<field name="res_model">account.analytic.line</field>
<field name="view_mode">pivot,graph</field>
<field name="domain">[('account_id.plan_id.name', '=', 'Programs')]</field>
<field name="context">{{
'pivot_measures': ['amount'],
'pivot_row_groupby': ['account_id'],
'pivot_column_groupby': ['date:quarter'],
}}</field>
</record>
<menuitem id="menu_nonprofit_root" name="Nonprofit" sequence="5" />
<menuitem id="menu_nonprofit_dashboard" name="Dashboard"
parent="menu_nonprofit_root" action="action_nonprofit_dashboard" />
<menuitem id="menu_grant_budget" name="Grant Budget Report"
parent="menu_nonprofit_root" action="action_grant_budget_report" />
</odoo>The key reports every nonprofit board expects:
- Statement of Financial Position — net assets by fund type, generated from the balance sheet with analytic filters.
- Statement of Activities — revenue and expenses by fund with net asset releases. The nonprofit income statement.
- Statement of Functional Expenses — natural categories (rows) by functional classification (program, management, fundraising).
- Donor Retention Report — YoY comparison of returning donors. Industry average is 43%; our clients hit 53%+.
- Grant Burn-Rate Dashboard — spending pace vs. timeline per active grant.
3 Nonprofit Odoo Mistakes That Create Audit Findings and Lost Funding
Commingling Restricted and Unrestricted Funds in a Single Account
A nonprofit receives a $50,000 restricted grant and deposits it into general operating. Without analytic tagging, the funds are indistinguishable from unrestricted donations. The finance team accidentally spends restricted funds on rent. The auditor flags it, the funder demands a return. We see this in 60% of first-time implementations.
Every restricted fund gets its own analytic account from day one. We add a server action that blocks journal entries without analytic tags from posting — forcing classification. The 5 minutes per entry saves 50 hours of audit remediation.
Issuing Incorrect Tax Receipts for Quid Pro Quo Donations
A $250 gala ticket that includes a $75 dinner has a deductible amount of $175, not $250. The IRS requires FMV disclosure for contributions over $75. Odoo's default invoice template shows only the total paid. If receipts overstate the deductible amount, both the nonprofit and donor face IRS penalties.
We create a custom QWeb tax receipt with three fields: total paid, FMV of goods received, and deductible amount. The FMV is stored on the product record. The template computes the deductible amount and includes IRS disclosure language automatically.
Not Releasing Temporarily Restricted Funds When Restrictions Are Met
Under FASB ASC 958, spending restricted funds on their intended purpose requires a "net asset release" journal entry. Many nonprofits skip this, leaving restricted balances overstated and unrestricted balances understated. The statements are materially misstated even though the cash is correctly spent.
We build a monthly scheduled action that scans expenses against temporarily restricted analytics and creates corresponding release entries — debiting restricted revenue, crediting unrestricted release. Fund balances stay accurate and your auditor sees releases in the Statement of Activities automatically.
What a Unified Nonprofit Platform Saves Your Organization
Replacing 6 disconnected tools with a single Odoo-based platform isn't about technology — it's about redirecting staff time from data entry to mission delivery:
Board reports, grant compliance reports, and donor analytics pull from a single database. No more exporting from 4 systems and merging in Excel. A finance director saves 15+ hours per month.
Automated thank-you emails within 24 hours, personalized year-end appeals based on giving history, and engagement scoring that flags at-risk donors before they lapse. The industry average is 43% — our clients hit 53%+.
Clean fund accounting with proper analytic tagging, automated net asset releases, and a complete audit trail on every transaction. Auditors spend less time, which means lower audit fees and zero management letter findings.
The hidden ROI is donor confidence. When a major donor asks "how was my $50,000 gift spent?", you pull a real-time report in 30 seconds showing every expense against that fund. That transparency turns one-time major donors into legacy supporters. Organizations that demonstrate stewardship at this level raise more — not because they ask better, but because they prove they manage better.