projects: Add Policies section with travel policy.
This commit is contained in:
parent
4367de57d1
commit
f1214a3c93
5 changed files with 1017 additions and 1 deletions
|
@ -0,0 +1,652 @@
|
||||||
|
{% extends "base_projects.html" %}
|
||||||
|
{% block subtitle %}Travel and Reimburseable Expense Policy - {% endblock %}
|
||||||
|
{% block submenuselection %}Policies{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1 id="software-freedom-conservancy-travel-and-reimbursable-expense-policy">Software Freedom Conservancy Travel and Reimbursable Expense Policy</h1>
|
||||||
|
<h2 id="overview">Overview</h2>
|
||||||
|
<p>This Travel and Reimbursable Expense Policy (“Policy”) applies to all
|
||||||
|
Conservancy Member Projects (“Projects”) of Software Freedom Conservancy
|
||||||
|
(“Conservancy”) and has been created to memorialize Conservancy’s
|
||||||
|
reimbursement policies relating to travel and other business expenses
|
||||||
|
incurred by Conservancy staff, Project Leadership Committee (“PLC”)
|
||||||
|
members, and project volunteers while engaged in business on behalf of, or
|
||||||
|
at the behest of Conservancy and/or a Project (“Travelers”).</p>
|
||||||
|
<p>This Policy includes an Easy Reference Guide that can be used as a
|
||||||
|
template for most of the travel covered under this Policy. When in doubt,
|
||||||
|
refer to the more detailed sections below.</p>
|
||||||
|
<h2 id="purpose">Purpose</h2>
|
||||||
|
<p>Conservancy must maintain effective control of business-related expenses
|
||||||
|
in order to maintain its financial viability and tax exempt status.
|
||||||
|
Conservancy and each Project is also accountable to our donors to ensure
|
||||||
|
that we manage their contributions wisely and maximize our ability to
|
||||||
|
pursue our charitable mission. As such, Conservancy expects Travelers to
|
||||||
|
use good judgment and to claim reimbursement for only those expenses that
|
||||||
|
are necessary and reasonable. Excessive expenses, including but not
|
||||||
|
limited to luxury accommodations and services unnecessary for, or unrelated
|
||||||
|
to the furtherance of Conservancy’s charitable mission are not eligible for
|
||||||
|
reimbursement.</p>
|
||||||
|
<p>Any travel expense that adheres to this Policy is considered In-Policy
|
||||||
|
and does not require special approval, so long as the trip itself
|
||||||
|
has been approved in writing by Conservancy’s Executive Director or
|
||||||
|
by a Project’s Leadership Committee (“PLC”) in a regular and documented
|
||||||
|
PLC vote. Conservancy and/or a PLC can limit allowable travel expenses
|
||||||
|
to an amount less than what would otherwise be considered acceptable
|
||||||
|
according to this Policy. If so, the smaller budget is the maximum
|
||||||
|
allowed expense.</p>
|
||||||
|
<p>PLC’s may, in fact, have their own travel policy that is more restrictive
|
||||||
|
than this one. Please consult the PLC for your Conservancy project before
|
||||||
|
incurring an expenses to ensure you understand what expenses can be
|
||||||
|
reimbursed.</p>
|
||||||
|
<h2 id="easy-reference-guide">Easy Reference Guide</h2>
|
||||||
|
<p>Travelers should adhere to the following guidelines to stay In-Policy.</p>
|
||||||
|
<h3 id="flights">Flights</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>Before you buy tickets, save a screenshot of a flight search that
|
||||||
|
shows the least expensive fare available from multiple airlines for
|
||||||
|
the dates you need to travel.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Book at least 14 days in advance.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Fares within $100 of that lowest fare you found are In-Policy, even if
|
||||||
|
you book on a different site or for different dates.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Fares over $750 domestically or $1,650 internationally require
|
||||||
|
Conservancy’s pre-approval.</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="hotels">Hotels</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>Hotels are In-Policy as long as the average nightly rate you pay
|
||||||
|
(excluding taxes and fees) is under the maximum rate for lodging for your
|
||||||
|
destination. See the “Rates” section below for details.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Be sure the receipt from your hotel includes the dates of your
|
||||||
|
stay, not just a total.</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="receipts">Receipts</h3>
|
||||||
|
<p>Keep and submit PDFs of the following, as applicable:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>Your fare search (i.e., a screen shot)</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Your plane and rail tickets (e-confirmation of purchase is sufficient)</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Your hotel/lodging invoice</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="per-diem">Per Diem</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>Use your per diem to cover meals and incidental expenses.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Your total per diem allowance follows the rates published for your
|
||||||
|
destination. See the “Rates” section below for details.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Try to be frugal: per diem rates are rather generous.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>When attending a conference where food is included, Do The Right
|
||||||
|
Thing and claim a lower per diem.</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="reimbursement">Reimbursement</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>To receive reimbursement, send transportation and hotel receipts,
|
||||||
|
along with a list of per diem days and totals, to
|
||||||
|
<a href="mailto:accounting@sfconservancy.org">accounting@sfconservancy.org</a>. Refer to the Expense Report
|
||||||
|
section below for details.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Your complete reimbursement request must be submitted to
|
||||||
|
<a href="mailto:accounting@sfconservancy.org">accounting@sfconservancy.org</a> within 90 days of the last date of
|
||||||
|
travel. Untimely requests <strong>will not be reimbursed</strong>.</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h2 id="rates">Rates</h2>
|
||||||
|
<p>Throughout this document, we refer to rates reported by other parties.</p>
|
||||||
|
<p>For travel in the United States, we follow the maximum rates for lodging and
|
||||||
|
M&IE per diem set by the
|
||||||
|
<a href="https://www.gsa.gov/perdiem">US General Services Administration</a>.</p>
|
||||||
|
<p>For travel outside the United States, we follow the maximum rates for lodging and
|
||||||
|
M&IE per diem set by the
|
||||||
|
<a href="https://aoprals.state.gov/web920/per_diem.asp">US Department of State</a>.</p>
|
||||||
|
<p>We calculate the total per diem allowance for a trip using the same method
|
||||||
|
as the GSA. Travelers may request up to 100% of the listed rate for each
|
||||||
|
full day of travel, plus 75% of the listed rate for each partial day of
|
||||||
|
travel. For example, if you fly to a conference on Monday, spend Tuesday
|
||||||
|
through Thursday at the conference, and return home on Friday, and the per
|
||||||
|
diem rate for the conference city is $80, you may request up to $360: $80
|
||||||
|
for each day Tuesday through Thursday, plus $60 for each day you flew.</p>
|
||||||
|
<p>When we convert currencies (e.g., to determine whether a hotel paid in Euros
|
||||||
|
was within the maximum lodging rate), we use the final rate published by
|
||||||
|
<a href="https://openexchangerates.org/">Open Exchange Rates</a> on the date we received
|
||||||
|
the reimbursement request. Please do not do your own currency conversions
|
||||||
|
in your reimbursement requests. Simply report expenses in their original
|
||||||
|
currency/ies, and we will convert appropriately. If you have questions or
|
||||||
|
concerns about our rates, just ask, and we’ll be happy to provide details
|
||||||
|
before we send you final payment.</p>
|
||||||
|
<h2 id="reimbursement-procedure">Reimbursement Procedure</h2>
|
||||||
|
<p>Conservancy handles reimbursements on a NET-30 basis, starting from the date
|
||||||
|
that complete materials are received. If this is an issue, Conservancy is
|
||||||
|
available to prepurchase expensive items like airline tickets on your
|
||||||
|
behalf, so that you don’t need to be reimbursed.</p>
|
||||||
|
<p>If you seek to be reimbursed for Conservancy Project expenses, please send
|
||||||
|
the following, in a self contained email (with attachments as necessary),
|
||||||
|
cc’ing your Project Leadership Committee address (PROJECT@sfconservancy.org)
|
||||||
|
for Project approval:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>A brief paragraph explaining what was accomplished for the project
|
||||||
|
during your travel and/or with the funds being reimbursed. This can
|
||||||
|
be informal; it’s just for our records to confirm the travel advanced
|
||||||
|
the not-for-profit mission of both Conservancy and the project.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>A brief report listing the items to be reimbursed.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Full receipt(s) for everything, unless the travel policy does not
|
||||||
|
require receipts (e.g., for per diem expenses).</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>If your receipts are in a different currency than your preferred one
|
||||||
|
for reimbursement, include documentation of the rate conversion (e.g., a
|
||||||
|
redacted credit card statement in your preferred currency). Otherwise,
|
||||||
|
Conservancy will use the prevailing rate for the date of the expense for
|
||||||
|
conversion.</p>
|
||||||
|
<p>Please verify that the receipts that you submit are within the attached
|
||||||
|
travel policy requirements. Note, however, that your Project Leadership
|
||||||
|
Committee may have set a stricter budget than what the general
|
||||||
|
Conservancy policy allows.</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>How you’d liked to be reimbursed. The four payment options, in order of
|
||||||
|
Conservancy’s preference, are:</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>PayPal. For this, we need (a) the email address that is registered to
|
||||||
|
the PayPal account that will receive reimbursement and (b) the preferred
|
||||||
|
currency. (Please verify that PayPal supports transactions in
|
||||||
|
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=p/sell/mc/mc_intro-outside">your preferred currency before choosing one</a>.)
|
||||||
|
Note that sometimes PayPal charges fees. Usually we’re taking funds
|
||||||
|
from our PayPal balance, which means no fees appear on our side, but
|
||||||
|
they may appear on yours. In our experience, USA PayPal account holders
|
||||||
|
physically in the USA are not typically charged fees; but we do advise
|
||||||
|
you to check PayPal’s fee schedule before choosing this method.</p>
|
||||||
|
<p>NOTE: Typically, when choosing PayPal as a payment option, amounts owed
|
||||||
|
in USD will be paid in USD, regardless of your preferred currency
|
||||||
|
selection.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Issue you a check in USD from a USA bank, sent to you via post.
|
||||||
|
For this option, I just need your postal address.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Wire the amount in your local currency to your bank account in your
|
||||||
|
country. For this option, I need as much of the following information
|
||||||
|
as it’s possible for you to collect.</p>
|
||||||
|
<p><strong>Required information</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>Full Name of the Account Holder</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Full Address of the Account Holder</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Account Number</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Preferred Currency</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Bank Name</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Bank Address</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Bank ACH or ABA routing number (for banks in the US);
|
||||||
|
or SWIFT and/or BIC code (for banks outside the US)</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Additional required information by country</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>All countries inside the EU: IBAN</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Australia: BSB or “Bank Code”</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Brazil: IABN; Tax ID of the Account Holder (your 14-digit CNPJ
|
||||||
|
or 11-digit CPF); Phone Number of the Account Holder;
|
||||||
|
and Bank Agency Code</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Colombia: Tax ID of the Account Holder; and Phone Number
|
||||||
|
of the Account Holder</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>India: IFSC Code</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Kenya: The name of the local branch of your bank where you hold your
|
||||||
|
account</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Qatar: IBAN</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Russian Federation: Beneficiary INN; Patronymic Name of Beneficiary;
|
||||||
|
and VAT/VO Code of Beneficiary Bank</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Ukraine: BSB or “Bank Code”; Tax ID of the Account Holder;
|
||||||
|
and Phone Number of the Account Holder</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>United States: Phone Number of the Account Holder</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Additional information we can use</strong></p>
|
||||||
|
<p>Banks outside the US will often designate a Correspondent Bank when
|
||||||
|
receiving funds from the US. If you can give us the Correspondent
|
||||||
|
Bank Name and ACH, we can specify that in the wire. Your bank will
|
||||||
|
usually take the fewest fees when receiving wires from its
|
||||||
|
Correspondent Bank, so this will mean the most money for you.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>A check in your local currency, sent to you via post. For this option, we
|
||||||
|
just need your postal address and what currency you want. Note that this
|
||||||
|
is a more involved option to process and will usually take at least 30
|
||||||
|
days to issue payment. We do not recommend this method. Please choose
|
||||||
|
it only if you absolutely cannot receive a wire transfer for some reason.</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>Project Leadership Committees: when you see emails of this nature, please
|
||||||
|
be sure to have your designated Representative review the materials and
|
||||||
|
send an approval message to Conservancy.</p>
|
||||||
|
<h2 id="project-leadership-committee-review">Project Leadership Committee Review</h2>
|
||||||
|
<p>Conservancy foresees the need for periodic reasonable exceptions to
|
||||||
|
this Policy. Persons working on behalf of a specific Project seeking
|
||||||
|
an exception to this Policy must petition their PLC to obtain written
|
||||||
|
approval from Conservancy authorizing the exception. Persons working
|
||||||
|
directly on behalf of Conservancy seeking an exception to the
|
||||||
|
Policy must obtain written approval from Conservancy authorizing the
|
||||||
|
exception.</p>
|
||||||
|
<p>PLCs are responsible for creating procedures for requesting exceptions,
|
||||||
|
and submitting to Conservancy reimbursement requests associated with
|
||||||
|
their respective Projects. PLCs are also responsible for making available
|
||||||
|
a list of required response times for inquiries, including but not
|
||||||
|
limited to, the following two cases</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>a specific number of days to respond to regular reimbursement requests,
|
||||||
|
and</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>a specific number of days to respond to pre-authorization requests.</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>PLCs are also responsible for monitoring the available balance in their
|
||||||
|
Project Fund, and for granting or refusing approval for travel expense
|
||||||
|
requests based on an assessment of the funds available and of any
|
||||||
|
outstanding contracts payable. PLCs are not to approve travel expense
|
||||||
|
requests when their Project does not have sufficient funds to cover the
|
||||||
|
expense. If a PLC has any questions regarding whether their Project has
|
||||||
|
sufficient funds to cover a Traveler’s expense request, the PLC should
|
||||||
|
contact Conservancy.</p>
|
||||||
|
<h2 id="transportation">Transportation</h2>
|
||||||
|
<h3 id="overall-transportation-cost">Overall transportation Cost</h3>
|
||||||
|
<p>Domestic transportation costs greater than US$750 requires Conservancy
|
||||||
|
approval prior to booking, even if all other Policy conditions have been
|
||||||
|
met. International transportation costs greater than US$1,800 requires
|
||||||
|
Conservancy approval prior to booking, even if all other Policy conditions
|
||||||
|
have been met.</p>
|
||||||
|
<h3 id="advance-purchase">Advance Purchase</h3>
|
||||||
|
<p>Tickets for travel by air or rail (excluding commuter train and subway)
|
||||||
|
should be booked at least 14 days in advance; any travel booked less than
|
||||||
|
14 days in advance requires written pre-authorization by Conservancy.
|
||||||
|
Tickets for travel by air or rail beyond 365 days in advance also require
|
||||||
|
written pre-authorization by Conservancy.</p>
|
||||||
|
<h3 id="air-travel">Air Travel</h3>
|
||||||
|
<h4 id="class-of-service">Class of Service</h4>
|
||||||
|
<p>Coach and/or Economy Airfare is the only acceptable class for all flights
|
||||||
|
(domestic and international) unless a PLC provides a special exception and
|
||||||
|
a valid reason (such as a need for business class due to a documented
|
||||||
|
medical reason) to Conservancy for written approval. Travelers may select
|
||||||
|
their airline of choice (e.g., for the purpose of collecting airline miles
|
||||||
|
and rewards), provided that the resulting fare otherwise meets the
|
||||||
|
requirements of this Policy. Travelers should not book out-of-Policy trips
|
||||||
|
(and thus pay a higher fare) in order to qualify for a mileage upgrade.</p>
|
||||||
|
<h4 id="advance-purchase_1">Advance Purchase</h4>
|
||||||
|
<p>Air travel should be booked at least 14 days in advance; any travel booked
|
||||||
|
less than 14 days in advance requires written pre-authorization by
|
||||||
|
Conservancy. Flights beyond 365 days in advance also require written
|
||||||
|
pre-authorization by Conservancy.</p>
|
||||||
|
<h4 id="low-fare">Low Fare</h4>
|
||||||
|
<p>Conservancy aims to balance cost savings with time savings and convenience.
|
||||||
|
Budgets for flights are set based on their travel time compared to the
|
||||||
|
flight with the lowest available fare. Flights with fares that are within
|
||||||
|
budget are in-Policy.</p>
|
||||||
|
<p>To find the lowest available fare, run a flight search that meets these
|
||||||
|
criteria, and save the results:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>The search must include fares from multiple airlines. Any
|
||||||
|
widely-recognized airfare search site that lists results from multiple
|
||||||
|
airlines is acceptable, including sites such as
|
||||||
|
<a href="http://orbitz.com">Orbitz</a>, <a href="http://kayak.com">Kayak</a> or
|
||||||
|
<a href="http://hipmunk.com/">Hipmunk</a>.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>The search must cover only the dates of relevant travel. For example, if
|
||||||
|
you’re attending a conference that runs Monday through Friday, the search
|
||||||
|
must have you arriving no earlier than Sunday, and leaving no later than
|
||||||
|
Saturday.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Except as allowed by Policy, the search must not use filters that might
|
||||||
|
exclude the least expensive fare. For example, you may filter out
|
||||||
|
flights with two or more connections, since Conservancy does not
|
||||||
|
consider those reasonable. However, you may not filter out specific
|
||||||
|
airlines, or flights without WiFi.</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>Save the results of this search. A PDF printout of the first page of
|
||||||
|
results from your browser is ideal. A screenshot can work too. Just make
|
||||||
|
sure the output shows the search criteria and the lowest available fare.
|
||||||
|
When you send your reimbursement request, attach these results.</p>
|
||||||
|
<p>The budget for a flight is set depending on how its cost and travel time
|
||||||
|
compares to the flight with the lowest available fare. Travel time is
|
||||||
|
measured from the scheduled departure time of the first flight in the
|
||||||
|
itinerary to the scheduled landing time of the final flight. We use the
|
||||||
|
following table to determine the budget:</p>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>If the travel time for a flight is…</th>
|
||||||
|
<th>the budget for that flight is…</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>the same or longer than the flight with the lowest available fare</td>
|
||||||
|
<td>the lowest available fare + US$100</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>less than three hours shorter</td>
|
||||||
|
<td>the lowest available fare + US$100</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>between three and six hours shorter</td>
|
||||||
|
<td>the lowest available fare + US$200</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>between six and ten hours shorter</td>
|
||||||
|
<td>the lowest available fare + US$350</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>at least ten hours shorter</td>
|
||||||
|
<td>the lowest available fare + US$600</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>Any flight with a total cost that is within its corresponding budget is
|
||||||
|
within Policy. Any flight with a cost over its budget requires written
|
||||||
|
pre-authorization by Conservancy.</p>
|
||||||
|
<p>Travelers may book their tickets on different dates or a different site as
|
||||||
|
long as they used a qualifying fare search site to determine that the
|
||||||
|
booked flights are within Policy.</p>
|
||||||
|
<h4 id="reasonable-flights">Reasonable Flights</h4>
|
||||||
|
<p>Conservancy asks that Travelers allow for flexibility with respect
|
||||||
|
to departure times during a desired day of travel, as well as longer
|
||||||
|
trips in order to reduce cost. However, Conservancy does consider
|
||||||
|
flights with two or more connections as unreasonable and does not
|
||||||
|
expect Travelers to consider those flight options to be reasonable.</p>
|
||||||
|
<h4 id="excess-baggage">Excess Baggage</h4>
|
||||||
|
<p>Should a team member travel on an airline that charges for a single piece of
|
||||||
|
checked baggage, such a baggage expense is eligible for reimbursement with a
|
||||||
|
receipt. Team members are responsible for charges on any baggage beyond a
|
||||||
|
single piece, unless that additional baggage is materials specifically
|
||||||
|
related to the Project’s and Conservancy’s mission (i.e., bringing t-shirts
|
||||||
|
and other promotional materials to an event).</p>
|
||||||
|
<h4 id="out-of-policy-bookings">Out-of-Policy Bookings</h4>
|
||||||
|
<p>All air travel not adhering to the above Policies are considered Out-of-Policy
|
||||||
|
and require written pre-authorization by an officer of Conservancy.</p>
|
||||||
|
<h4 id="cancellation-fees">Cancellation Fees</h4>
|
||||||
|
<p>Cancellation fees and other penalties incurred result of a change
|
||||||
|
of plans are reimbursable at Conservancy’s discretion. In general,
|
||||||
|
Conservancy shall reimburse such fees if the Traveler can submit a
|
||||||
|
valid reason for the change of plans. Acceptable reasons include Conservancy
|
||||||
|
and/or the PLC canceling or altering the trip or unexpected delays
|
||||||
|
in flight connections. In instances where these fees are incurred
|
||||||
|
without adequate explanation, Conservancy reserves the right to refuse
|
||||||
|
to reimburse the cost of the fees.</p>
|
||||||
|
<h3 id="other-transportation">Other Transportation</h3>
|
||||||
|
<h4 id="ground-transportation">Ground Transportation</h4>
|
||||||
|
<p>Ground transportation necessary as part of authorized Project trips
|
||||||
|
is considered to be a reasonable expense. Public ground transportation,
|
||||||
|
such as taxis, shuttles, buses and municipal transit, are generally
|
||||||
|
the most cost-effective options and are the standard for eligible
|
||||||
|
ground transportation reimbursements. All car rentals require pre-authorization
|
||||||
|
by the PLC or by an officer of Conservancy. When car rentals
|
||||||
|
have been pre-approved, the rental of compact cars is encouraged;
|
||||||
|
mid-size vehicles are authorized when necessary (e.g., when compact-sized
|
||||||
|
vehicles are not available or the number of passengers or volume of
|
||||||
|
baggage makes a compact vehicle impractical).</p>
|
||||||
|
<h4 id="rail-transportation">Rail Transportation</h4>
|
||||||
|
<p>Rail transportation as a means of travel for an authorized Project
|
||||||
|
trip is considered to be a reasonable expense. All rail transportation
|
||||||
|
must be in economy and/or coach class.</p>
|
||||||
|
<h4 id="use-of-personal-vehicles">Use of Personal Vehicles</h4>
|
||||||
|
<p>When circumstances require Travelers to utilize their personal vehicles for
|
||||||
|
Project purposes, they can be reimbursed at the current
|
||||||
|
<a href="https://www.irs.gov/tax-professionals/standard-mileage-rates/">USA IRS Standard Mileage Rate</a>,
|
||||||
|
plus any related parking expenses and toll fees. Drivers are encouraged to
|
||||||
|
find the lowest cost parking area reasonably near their destination.</p>
|
||||||
|
<h2 id="additional-days-of-travel">Additional Days of Travel</h2>
|
||||||
|
<p>Travelers often seek to add extra days before or after an approved trip
|
||||||
|
(e.g., the weekend before a conference). A Traveler may seek approval for
|
||||||
|
the expenses associated with an extended stay prior to booking the trip,
|
||||||
|
provided that the additional days are solely to enable a Traveler to
|
||||||
|
conduct work within the PLC’s objectives and Conservancy’s charitable
|
||||||
|
mission, or to get a particular airfare that <strong>reduces</strong> the overall cost of
|
||||||
|
the trip. Travelers may seek approval to book travel itineraries that
|
||||||
|
include extra days for personal reasons, so long as the cost of the flight
|
||||||
|
meets the other requirements of this Policy. Other expenses incurred
|
||||||
|
during extra personal days beyond transportation costs are not reimbursable.</p>
|
||||||
|
<h2 id="lodging">Lodging</h2>
|
||||||
|
<p>Travelers are expected to be cost-conscious and prudent when booking lodging
|
||||||
|
for approved trips, and to verify that rates are within the maximum lodging
|
||||||
|
rates for the hotel’s location. See the “Rates” section above for details.</p>
|
||||||
|
<p>If the lodging chosen by the Traveler and/or the PLC exceeds the maximum
|
||||||
|
lodging rate for the given location (per Traveler), the Traveler and/or the
|
||||||
|
PLC <strong>must</strong> obtain written pre-approval from Conservancy and the PLC before
|
||||||
|
booking the hotel. If written pre-approval is not sought or is not granted,
|
||||||
|
Conservancy will only reimburse up to the maximum lodging rate.</p>
|
||||||
|
<p>Lodging documentation submitted as part of a reimbursement request must
|
||||||
|
include a copy of the hotel invoice detailing all charges (credit card
|
||||||
|
receipts <strong>alone</strong> are unacceptable). In particular, since Conservancy only
|
||||||
|
reimburses for room charges (plus relevant taxes and fees) for the necessary
|
||||||
|
travel dates, the receipt from the hotel must clearly show the dates of stay,
|
||||||
|
and separately list room charges and any food or service charges.
|
||||||
|
Conservancy will not reimburse Travelers for any costs associated with an
|
||||||
|
upgrade of room accommodations.</p>
|
||||||
|
<p>In some cases, Conservancy, upon consultation with the PLC, may decide to
|
||||||
|
book lodging on behalf of Travelers. In this case, Conservancy-booked
|
||||||
|
lodging is always considered In-Policy.</p>
|
||||||
|
<h2 id="other-reimbursable-expenses">Other Reimbursable Expenses</h2>
|
||||||
|
<p>Conservancy will reimburse persons for Project-related expenses that
|
||||||
|
are incurred while traveling on approved Project business and/or approved
|
||||||
|
Conservancy business. Only necessary, ordinary and reasonable expenses
|
||||||
|
are eligible for reimbursement, and only those categories of expenses
|
||||||
|
listed in this document qualify.</p>
|
||||||
|
<h3 id="meals-and-incidental-expenses">Meals and Incidental Expenses</h3>
|
||||||
|
<h4 id="overview_1">Overview</h4>
|
||||||
|
<p>Travelers can submit for a per diem for meals and incidental expenses for
|
||||||
|
every day of a trip devoted to Project- and/or Conservancy-related mission
|
||||||
|
work, including the day(s) of travel itself, up to the maximum rate for the
|
||||||
|
destination of the trip. See the “Rates” section above for details.</p>
|
||||||
|
<p>These per diem rates are the maximum daily rate Travelers can claim. If a
|
||||||
|
conference has provided food, or food is provided in some other form, or
|
||||||
|
the costs the Traveler incurs are lower than this rate, then the Traveler
|
||||||
|
should reasonably reduce their per diem claim.</p>
|
||||||
|
<p>PLCs and/or Conservancy have the authority to set lower per diem rates
|
||||||
|
than those generated by the calculators above. In those instances,
|
||||||
|
Travelers will only be able to submit for the lower per diem rates.</p>
|
||||||
|
<h4 id="group-meals">Group Meals</h4>
|
||||||
|
<p>For groups of Travelers on an In-Policy trip, each Traveler should
|
||||||
|
pay for his/her own meals, seeing as all participants will have an
|
||||||
|
opportunity to submit for separate per diem reimbursements after the trip.</p>
|
||||||
|
<p>For clarification purposes, this Policy does not relate to planned
|
||||||
|
group events that include meals and/or refreshments (e.g., a PLC-organized
|
||||||
|
conference that includes lunch for all attendees). Further, PLCs and/or
|
||||||
|
Conservancy retain the right to allocate a separate budget for anticipated
|
||||||
|
large group meals beyond the individual per diem limits of each Traveler,
|
||||||
|
provided that they are within the PLC’s technical objectives and/or
|
||||||
|
Conservancy’s mission. Travelers anticipating a need to cover such
|
||||||
|
a large group meal should seek pre-approval from his/her PLC and/or Conservancy
|
||||||
|
for such expenses before the trip.</p>
|
||||||
|
<p>For any such group meal, Conservancy will require a written paragraph
|
||||||
|
summary of the meeting, indicating what was accomplished for the Project’s
|
||||||
|
and Conservancy’s mission.</p>
|
||||||
|
<h4 id="meals-for-organizational-development">Meals For Organizational Development</h4>
|
||||||
|
<p>Travelers may occasionally have the need to invite third parties
|
||||||
|
(e.g., prospective donors, contributors, community members, etc.) to
|
||||||
|
meals in order to further a PLC’s technical direction and/or Conservancy’s
|
||||||
|
mission. Conservancy recommends that Travelers seek pre-approval from
|
||||||
|
their PLC and/or Conservancy for such meals.</p>
|
||||||
|
<p>For any such organizational development meal, Conservancy will require a
|
||||||
|
written paragraph summary of the meeting, indicating what was accomplished
|
||||||
|
for the Project’s and Conservancy’s mission.</p>
|
||||||
|
<h4 id="phone-call-charges-part-of-per-diem">Phone Call Charges Part of Per Diem</h4>
|
||||||
|
<p>Charges for personal phone calls (e.g., made from a hotel, or via
|
||||||
|
a mobile phone in international travel) are not reimbursable as an
|
||||||
|
expense separate from the allocated per diem.</p>
|
||||||
|
<h4 id="currency-conversion-charges-part-of-per-diem">Currency Conversion Charges Part of Per Diem</h4>
|
||||||
|
<p>Any fees associated with currency conversion are not reimbursable as an
|
||||||
|
expense separate from the allocated per diem.</p>
|
||||||
|
<h3 id="conference-registration-fees">Conference Registration Fees</h3>
|
||||||
|
<p>Conservancy will reimburse conference registration fees up to $100 per day
|
||||||
|
for Travelers on approved Project business and/or approved Conservancy
|
||||||
|
business. For example, a $250 registration fee for a 3-day conference is
|
||||||
|
In-Policy; however, a $225 registration fee for a 2-day conference is not.</p>
|
||||||
|
<p>Travelers seeking reimbursement for registration fees that exceed $100 per
|
||||||
|
day must obtain prior approval from an officer of Conservancy.</p>
|
||||||
|
<h3 id="internet-access">Internet Access</h3>
|
||||||
|
<p>Internet access/wi-fi fees charged by a hotel are reimbursable, provided
|
||||||
|
that they are listed on the hotel/lodging invoice submitted for
|
||||||
|
reimbursement. Other internet access fees (e.g., airport internet
|
||||||
|
services, personal wi-fi hotspots, internet cafes) are not reimbursable
|
||||||
|
except as incidental expenses to be covered by a Traveler’s per diem.</p>
|
||||||
|
<h2 id="non-reimbursable-expenses">Non-reimbursable Expenses</h2>
|
||||||
|
<p>Non-reimbursable expenses are identified throughout this policy. The
|
||||||
|
following items are typically non-reimbursable expenses:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>Partner, spouse, and/or companion travel</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>First class travel (unless medically necessary)</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Upgrades to air travel, car rentals, or hotel rooms</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Purchase of clothing, luggage, toiletries and other miscellaneous
|
||||||
|
personal items</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Supplemental travel or car rental insurance</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Fines, penalties, or legal fees</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Personal entertainment or recreational expenses beyond the allotted per
|
||||||
|
diem</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>Travelers are permitted to pay for their own upgrades, or use bonus
|
||||||
|
programs to upgrade Conservancy-reimbursed expenses. However, Travelers
|
||||||
|
must ensure that Conservancy does not receive nor reimburse any charges
|
||||||
|
for any such transaction.</p>
|
||||||
|
<h2 id="satisfaction-of-irs-requirements">Satisfaction of IRS Requirements</h2>
|
||||||
|
<p>Reimbursed travel expenses are subject to examination by the USA Internal
|
||||||
|
Revenue Service (IRS). Travelers are responsible for retaining documentary
|
||||||
|
evidence that all expenses are strictly for Project- and/or
|
||||||
|
Conservancy-related purposes, not personal in nature, and therefore not
|
||||||
|
includable as taxable income to the Traveler. Receipts are required for
|
||||||
|
all expenses, no matter the amount.</p>
|
||||||
|
<h2 id="approvals">Approvals</h2>
|
||||||
|
<p>Travelers traveling on behalf of a Project must seek approvals and
|
||||||
|
submit expense reports to their PLC. PLCs are to review those expense
|
||||||
|
reports and pass them along to Conservancy’s accounting office for
|
||||||
|
final approval and reimbursement.</p>
|
||||||
|
<p>Travelers traveling on behalf of Conservancy must seek approvals from
|
||||||
|
Conservancy’s Executive Director, and submit expense reports to
|
||||||
|
Conservancy’s accounting office for reimbursement.</p>
|
||||||
|
<h2 id="expense-reporting">Expense Reporting</h2>
|
||||||
|
<p>Travelers seeking reimbursement must submit an expense report to the
|
||||||
|
appropriate channel with the following information:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>Name of Traveler</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Brief description of trip and trip’s purpose (e.g., “August 2011
|
||||||
|
trip to XYZ conference for ABC project, served as planning committee
|
||||||
|
member”; “Feb. ‘12 FOO hackfest in Portland, OR; contributed code”)</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>A brief paragraph explaining what was accomplished for the project
|
||||||
|
during your travel.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Number of days traveled (with documentary evidence, e.g., conference
|
||||||
|
itinerary, etc.) and associated per diem</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>List of expenses not covered by per diem (e.g., transportation, lodging)
|
||||||
|
with substantiating receipts (or scans of receipts).</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>In the event that it is impractical to obtain a required receipt and/or if
|
||||||
|
such receipt has been inadvertently destroyed or lost, the Traveler should
|
||||||
|
furnish a written statement to that effect, as well as an explanation of
|
||||||
|
the expenditure involved. When possible, secondary documentation (such as
|
||||||
|
a redacted credit card bill) should be provided instead of the
|
||||||
|
lost/destroyed receipt.</p>
|
||||||
|
<p>Any expense without a substantiated receipt and/or a supporting written
|
||||||
|
statement will not be reimbursed.</p>
|
||||||
|
<p>Conservancy requests that all expense reports be submitted within two weeks
|
||||||
|
of travel. Expense reports filed more than 90 days after the last day of
|
||||||
|
travel (or for other reimbursable expenses, the day expenses are incurred)
|
||||||
|
<strong>will not be reimbursed</strong>.</p>
|
||||||
|
<p>Reimbursements are paid by Conservancy on a NET-30 basis, from the
|
||||||
|
date of receipt by Conservancy of the fully complete report and supporting
|
||||||
|
documentation for the travel.</p>
|
||||||
|
<h2 id="consequences-of-policy-violations">Consequences of Policy Violations</h2>
|
||||||
|
<p>Failure to comply with this policy may result in the denial of, or delay
|
||||||
|
in payment for, reimbursement requests.</p>
|
||||||
|
<h2 id="policy-changes">Policy Changes</h2>
|
||||||
|
<p>The Conservancy reserves the right to change any terms of this Policy
|
||||||
|
from time to time. The Policy of record shall be the Policy most recently
|
||||||
|
distributed by the Conservancy. </p>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1 @@
|
||||||
|
conservancy-travel-policy.783dcdd92fc61f3f150e1c65782c0fe527c8ff52.html
|
16
www/conservancy/static/projects/policies/index.html
Normal file
16
www/conservancy/static/projects/policies/index.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{% extends "base_projects.html" %}
|
||||||
|
{% block subtitle %}Member Project Policies - {% endblock %}
|
||||||
|
{% block submenuselection %}Policies{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>Member Project Policies</h1>
|
||||||
|
|
||||||
|
<p>These are the policies that member projects follow in working with Conservancy. These pages are provided as a reference to all projects participants.</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="conservancy-travel-policy.html">Travel and reimburseable expense policy</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>For more background about the policies, including licensing and change requests, please refer to <a href="https://k.sfconservancy.org/policies">their source code in Git</a>.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
346
www/conservancy/static/projects/policies/publish-travel-policy.py
Executable file
346
www/conservancy/static/projects/policies/publish-travel-policy.py
Executable file
|
@ -0,0 +1,346 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import contextlib
|
||||||
|
import functools
|
||||||
|
import locale
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
try:
|
||||||
|
import markdown
|
||||||
|
from markdown.extensions import tables as mdx_tables
|
||||||
|
from markdown.extensions import sane_lists as mdx_sane_lists
|
||||||
|
from markdown.extensions import smarty as mdx_smarty
|
||||||
|
from markdown.extensions import toc as mdx_toc
|
||||||
|
markdown_import_success = True
|
||||||
|
except ImportError:
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise
|
||||||
|
markdown_import_success = False
|
||||||
|
|
||||||
|
TEMPLATE_HEADER = """{% extends "base_projects.html" %}
|
||||||
|
{% block subtitle %}Travel and Reimburseable Expense Policy - {% endblock %}
|
||||||
|
{% block submenuselection %}Policies{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
TEMPLATE_FOOTER = """
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
"""
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def run(cmd, encoding=None, ok_exitcodes=frozenset([0]), **kwargs):
|
||||||
|
kwargs.setdefault('stdout', subprocess.PIPE)
|
||||||
|
if encoding is None:
|
||||||
|
mode = 'rb'
|
||||||
|
no_data = b''
|
||||||
|
else:
|
||||||
|
mode = 'r'
|
||||||
|
no_data = ''
|
||||||
|
with contextlib.ExitStack() as exit_stack:
|
||||||
|
proc = exit_stack.enter_context(subprocess.Popen(cmd, **kwargs))
|
||||||
|
pipes = [exit_stack.enter_context(open(
|
||||||
|
getattr(proc, name).fileno(), mode, encoding=encoding, closefd=False))
|
||||||
|
for name in ['stdout', 'stderr']
|
||||||
|
if kwargs.get(name) is subprocess.PIPE]
|
||||||
|
if pipes:
|
||||||
|
yield (proc, *pipes)
|
||||||
|
else:
|
||||||
|
yield proc
|
||||||
|
for pipe in pipes:
|
||||||
|
for _ in iter(lambda: pipe.read(4096), no_data):
|
||||||
|
pass
|
||||||
|
if proc.returncode not in ok_exitcodes:
|
||||||
|
raise subprocess.CalledProcessError(proc.returncode, cmd)
|
||||||
|
|
||||||
|
class GitPath:
|
||||||
|
GIT_BIN = shutil.which('git')
|
||||||
|
CLEAN_ENV = {k: v for k, v in os.environ.items() if not k.startswith('GIT_')}
|
||||||
|
ANY_EXITCODE = range(-256, 257)
|
||||||
|
IGNORE_ERRORS = {
|
||||||
|
'ok_exitcodes': ANY_EXITCODE,
|
||||||
|
'stderr': subprocess.DEVNULL,
|
||||||
|
}
|
||||||
|
STATUS_CLEAN_OR_UNMANAGED = frozenset(' ?')
|
||||||
|
|
||||||
|
def __init__(self, path, encoding, env=None):
|
||||||
|
self.path = path
|
||||||
|
self.dir_path = path if path.is_dir() else path.parent
|
||||||
|
self.encoding = encoding
|
||||||
|
self.run_defaults = {
|
||||||
|
'cwd': str(self.dir_path),
|
||||||
|
'env': env,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def can_run(cls):
|
||||||
|
return cls.GIT_BIN is not None
|
||||||
|
|
||||||
|
def _run(self, cmd, encoding=None, ok_exitcodes=frozenset([0]), **kwargs):
|
||||||
|
return run(cmd, encoding, ok_exitcodes, **self.run_defaults, **kwargs)
|
||||||
|
|
||||||
|
def _cache(orig_func):
|
||||||
|
attr_name = '_cached_' + orig_func.__name__
|
||||||
|
@functools.wraps(orig_func)
|
||||||
|
def cache_wrapper(self):
|
||||||
|
try:
|
||||||
|
return getattr(self, attr_name)
|
||||||
|
except AttributeError:
|
||||||
|
setattr(self, attr_name, orig_func(self))
|
||||||
|
return getattr(self, attr_name)
|
||||||
|
return cache_wrapper
|
||||||
|
|
||||||
|
@_cache
|
||||||
|
def is_work_tree(self):
|
||||||
|
with self._run([self.GIT_BIN, 'rev-parse', '--is-inside-work-tree'],
|
||||||
|
self.encoding, **self.IGNORE_ERRORS) as (_, stdout):
|
||||||
|
return stdout.readline() == 'true\n'
|
||||||
|
|
||||||
|
@_cache
|
||||||
|
def status_lines(self):
|
||||||
|
with self._run([self.GIT_BIN, 'status', '-z'],
|
||||||
|
self.encoding) as (_, stdout):
|
||||||
|
return stdout.read().split('\0')
|
||||||
|
|
||||||
|
@_cache
|
||||||
|
def has_managed_modifications(self):
|
||||||
|
return any(line and line[1] not in self.STATUS_CLEAN_OR_UNMANAGED
|
||||||
|
for line in self.status_lines())
|
||||||
|
|
||||||
|
@_cache
|
||||||
|
def has_staged_changes(self):
|
||||||
|
return any(line and line[0] not in self.STATUS_CLEAN_OR_UNMANAGED
|
||||||
|
for line in self.status_lines())
|
||||||
|
|
||||||
|
def commit_at(self, revision):
|
||||||
|
with self._run([self.GIT_BIN, 'rev-parse', revision],
|
||||||
|
self.encoding) as (_, stdout):
|
||||||
|
return stdout.readline().rstrip('\n') or None
|
||||||
|
|
||||||
|
@_cache
|
||||||
|
def upstream_commit(self):
|
||||||
|
return self.commit_at('@{upstream}')
|
||||||
|
|
||||||
|
@_cache
|
||||||
|
def head_commit(self):
|
||||||
|
return self.commit_at('HEAD')
|
||||||
|
|
||||||
|
def in_sync_with_upstream(self):
|
||||||
|
return self.upstream_commit() == self.head_commit()
|
||||||
|
|
||||||
|
@_cache
|
||||||
|
def last_commit(self):
|
||||||
|
with self._run([self.GIT_BIN, 'log', '-n1', '--format=format:%H', self.path.name],
|
||||||
|
self.encoding, **self.IGNORE_ERRORS) as (_, stdout):
|
||||||
|
return stdout.readline().rstrip('\n') or None
|
||||||
|
|
||||||
|
def operate(self, subcmd, ok_exitcodes=frozenset([0])):
|
||||||
|
with self._run([self.GIT_BIN, *subcmd], None, ok_exitcodes, stdout=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def add_parser_flag(argparser, dest, **kwargs):
|
||||||
|
kwargs.update(dest=dest, default=None)
|
||||||
|
switch_root = dest.replace('_', '-')
|
||||||
|
switch = '--' + switch_root
|
||||||
|
argparser.add_argument(switch, **kwargs, action='store_true')
|
||||||
|
kwargs['help'] = "Do not do {}".format(switch)
|
||||||
|
argparser.add_argument('--no-' + switch_root, **kwargs, action='store_false')
|
||||||
|
|
||||||
|
def parse_arguments(arglist):
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
epilog="""By default, the program will pull from Git if the output path
|
||||||
|
is a Git checkout with a tracking branch, and will commit and push if
|
||||||
|
that checkout is in sync with the tracking branch without any staged changes.
|
||||||
|
Setting any flag will always override the default behavior.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--encoding', '-E',
|
||||||
|
default=locale.getpreferredencoding(),
|
||||||
|
help="Encoding to use for all I/O. "
|
||||||
|
"Default is your locale's encoding.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--revision', '-r',
|
||||||
|
help="Revision string to version the published page. "
|
||||||
|
"Default determined from the revision of the source file.",
|
||||||
|
)
|
||||||
|
add_parser_flag(
|
||||||
|
parser, 'pull',
|
||||||
|
help="Try to pull the remote tracking branch to make the checkout "
|
||||||
|
"up-to-date before making changes"
|
||||||
|
)
|
||||||
|
add_parser_flag(
|
||||||
|
parser, 'commit',
|
||||||
|
help="Commit changes to the travel policy",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-m', dest='commit_message',
|
||||||
|
default="Publish {filename} revision {revision}.",
|
||||||
|
help="Message for any commit",
|
||||||
|
)
|
||||||
|
add_parser_flag(
|
||||||
|
parser, 'push',
|
||||||
|
help="Push to the remote tracking branch after committing changes",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'input_path', type=pathlib.Path,
|
||||||
|
help="Path to the Conservancy travel policy Markdown source",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'output_path', type=pathlib.Path,
|
||||||
|
nargs='?', default=pathlib.Path(__file__).parent,
|
||||||
|
help="Path to the directory to write output files",
|
||||||
|
)
|
||||||
|
|
||||||
|
if not markdown_import_success:
|
||||||
|
parser.error("""markdown module is not installed.
|
||||||
|
Try `apt install python3-markdown` or `python3 -m pip install --user Markdown`.""")
|
||||||
|
|
||||||
|
args = parser.parse_args(arglist)
|
||||||
|
args.git_output = GitPath(args.output_path, args.encoding)
|
||||||
|
if args.pull or args.commit or args.push:
|
||||||
|
if not args.git_output.can_run():
|
||||||
|
parser.error("Git operation requested but `git` not found in PATH")
|
||||||
|
elif not args.git_output.is_work_tree():
|
||||||
|
parser.error("Git operation requested but {} is not a working path".format(
|
||||||
|
args.output_path.as_posix()))
|
||||||
|
if args.revision is None:
|
||||||
|
try:
|
||||||
|
args.revision = GitPath(args.input_path, args.encoding, GitPath.CLEAN_ENV).last_commit()
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
pass
|
||||||
|
if args.revision is None:
|
||||||
|
parser.error("no --revision specified and not found from input path")
|
||||||
|
args.output_link_path = args.git_output.dir_path / 'conservancy-travel-policy.html'
|
||||||
|
args.output_file_path = args.output_link_path.with_suffix('.{}.html'.format(args.revision))
|
||||||
|
return args
|
||||||
|
|
||||||
|
class GitOperation:
|
||||||
|
def __init__(self, args):
|
||||||
|
self.args = args
|
||||||
|
self.git_path = args.git_output
|
||||||
|
self.exitcode = None
|
||||||
|
self.on_work_tree = self.git_path.can_run() and self.git_path.is_work_tree()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
arg_state = getattr(self.args, self.NAME)
|
||||||
|
if arg_state is None:
|
||||||
|
arg_state = self.should_run()
|
||||||
|
if not arg_state:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.exitcode = self.run_git() or 0
|
||||||
|
except subprocess.CalledProcessError as error:
|
||||||
|
self.exitcode = error.returncode
|
||||||
|
|
||||||
|
|
||||||
|
class GitPull(GitOperation):
|
||||||
|
NAME = 'pull'
|
||||||
|
|
||||||
|
def should_run(self):
|
||||||
|
return self.on_work_tree and not self.git_path.has_staged_changes()
|
||||||
|
|
||||||
|
def run_git(self):
|
||||||
|
self.git_path.operate(['fetch', '--no-tags'])
|
||||||
|
self.git_path.operate(['merge', '--ff-only'])
|
||||||
|
|
||||||
|
|
||||||
|
class GitCommit(GitOperation):
|
||||||
|
NAME = 'commit'
|
||||||
|
VERB = 'committed'
|
||||||
|
|
||||||
|
def __init__(self, args):
|
||||||
|
super().__init__(args)
|
||||||
|
try:
|
||||||
|
self._should_run = ((not self.git_path.has_staged_changes())
|
||||||
|
and self.git_path.in_sync_with_upstream())
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
self._should_run = False
|
||||||
|
|
||||||
|
def should_run(self):
|
||||||
|
return self.on_work_tree and self._should_run
|
||||||
|
|
||||||
|
def run_git(self):
|
||||||
|
self.git_path.operate([
|
||||||
|
'add', str(self.args.output_file_path), str(self.args.output_link_path),
|
||||||
|
])
|
||||||
|
commit_message = self.args.commit_message.format(
|
||||||
|
filename=self.args.output_link_path.name,
|
||||||
|
revision=self.args.revision,
|
||||||
|
)
|
||||||
|
self.git_path.operate(['commit', '-m', commit_message])
|
||||||
|
|
||||||
|
|
||||||
|
class GitPush(GitCommit):
|
||||||
|
NAME = 'push'
|
||||||
|
VERB = 'pushed'
|
||||||
|
|
||||||
|
def run_git(self):
|
||||||
|
self.git_path.operate(['push'])
|
||||||
|
|
||||||
|
|
||||||
|
def write_output(args):
|
||||||
|
converter = markdown.Markdown(
|
||||||
|
extensions=[
|
||||||
|
mdx_tables.TableExtension(),
|
||||||
|
mdx_sane_lists.SaneListExtension(),
|
||||||
|
mdx_smarty.SmartyExtension(),
|
||||||
|
mdx_toc.TocExtension(),
|
||||||
|
],
|
||||||
|
output_format='html5',
|
||||||
|
)
|
||||||
|
with args.input_path.open(encoding=args.encoding) as src_file:
|
||||||
|
body = converter.convert(src_file.read())
|
||||||
|
with tempfile.NamedTemporaryFile(
|
||||||
|
'w',
|
||||||
|
encoding=args.encoding,
|
||||||
|
dir=args.git_output.dir_path.as_posix(),
|
||||||
|
suffix='.html',
|
||||||
|
delete=False,
|
||||||
|
) as tmp_out:
|
||||||
|
try:
|
||||||
|
tmp_out.write(TEMPLATE_HEADER)
|
||||||
|
tmp_out.write(body)
|
||||||
|
tmp_out.write(TEMPLATE_FOOTER)
|
||||||
|
tmp_out.flush()
|
||||||
|
os.rename(tmp_out.name, str(args.output_file_path))
|
||||||
|
except BaseException:
|
||||||
|
os.unlink(tmp_out.name)
|
||||||
|
raise
|
||||||
|
if args.output_link_path.is_symlink():
|
||||||
|
args.output_link_path.unlink()
|
||||||
|
args.output_link_path.symlink_to(args.output_file_path.name)
|
||||||
|
|
||||||
|
def main(arglist=None, stdout=sys.stdout, stderr=sys.stderr):
|
||||||
|
args = parse_arguments(arglist)
|
||||||
|
pull = GitPull(args)
|
||||||
|
pull.run()
|
||||||
|
if pull.exitcode:
|
||||||
|
return pull.exitcode
|
||||||
|
write_output(args)
|
||||||
|
ops = [GitCommit(args), GitPush(args)]
|
||||||
|
for op in ops:
|
||||||
|
op.run()
|
||||||
|
if op.exitcode != 0:
|
||||||
|
exitcode = op.exitcode or 0
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
exitcode = 0
|
||||||
|
print(args.input_path.name, "converted,",
|
||||||
|
", ".join(op.VERB if op.exitcode == 0 else "not " + op.VERB for op in ops),
|
||||||
|
file=stdout)
|
||||||
|
return exitcode
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
exit(main())
|
||||||
|
|
|
@ -5,8 +5,9 @@
|
||||||
<h2>{% block category %}Projects{% endblock %} & Services</h2>
|
<h2>{% block category %}Projects{% endblock %} & Services</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li class="Current"><a href="/projects/current/">Current Member Projects</a></li>
|
<li class="Current"><a href="/projects/current/">Current Member Projects</a></li>
|
||||||
<li class="Services"><a href="/projects/services/">Member Project Services</a></li>
|
|
||||||
<li class="Applying"><a href="/projects/apply/">Applying</a></li>
|
<li class="Applying"><a href="/projects/apply/">Applying</a></li>
|
||||||
|
<li class="Services"><a href="/projects/services/">Member Project Services</a></li>
|
||||||
|
<li class="Policies"><a href="/projects/policies/">Member Project Policies</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div id="mainContent">{% block content %}{% endblock %}
|
<div id="mainContent">{% block content %}{% endblock %}
|
||||||
|
|
Loading…
Reference in a new issue