365 lines
11 KiB
PHP
365 lines
11 KiB
PHP
<?php
|
|
|
|
class CRM_Tbusainvoicegen_Timebank {
|
|
|
|
const TBBILLABLEFIELD = 'custom_233';
|
|
const BILLABLEMEMBERFIELD = 'custom_236';
|
|
const BILLINGPERIODFIELD = 'custom_228';
|
|
const TBCREATED = 'custom_114';
|
|
const INVOICEDATE = 'custom_244';
|
|
const DUEDATE = 'custom_245';
|
|
const PERIODBEGIN = 'custom_246';
|
|
const PERIODEND = 'custom_247';
|
|
const BILLABLEMEMBERSCONTRIB = 'custom_249';
|
|
const BIANNUALFEERATE = 'custom_250';
|
|
const NUMBEROFMEMBERSOFFSET = 'custom_253';
|
|
|
|
/**
|
|
* Set the pricing here.
|
|
* Up to the number of members in single quotes means you pay the second number every 6 months.
|
|
* @var array
|
|
*/
|
|
private $priceArray = [
|
|
'34' => 30,
|
|
'49' => 60,
|
|
'79' => 90,
|
|
'99' => 120,
|
|
'119' => 160,
|
|
'149' => 200,
|
|
'199' => 240,
|
|
'249' => 340,
|
|
'349' => 440,
|
|
'499' => 590,
|
|
'749' => 880,
|
|
'1000' => 1200,
|
|
'9999999' => 9999,
|
|
];
|
|
|
|
/**
|
|
* The contact ID of this timebank.
|
|
* @var int
|
|
*/
|
|
private $contactId;
|
|
|
|
/**
|
|
* The number of members.
|
|
* @var int
|
|
*/
|
|
private $memberCount;
|
|
|
|
/**
|
|
* The number of billable members. It's $memberCount -2 with a minimum of zero.
|
|
* @var int
|
|
*/
|
|
private $memberCountiBillable;
|
|
|
|
/**
|
|
* The price for one period of this timebank.
|
|
* @var float
|
|
*/
|
|
private $price;
|
|
|
|
/**
|
|
* The creation date of the timebank.
|
|
* @var DateTime
|
|
*/
|
|
private $creationDate;
|
|
|
|
/**
|
|
* The date the invoice is generated.
|
|
* @var DateTime
|
|
*/
|
|
private $invoiceDate;
|
|
|
|
/**
|
|
* The date the invoice is due.
|
|
* @var DateTime
|
|
*/
|
|
private $invoiceDueDate;
|
|
|
|
/**
|
|
* The date the invoice period begins.
|
|
* @var DateTime
|
|
*/
|
|
private $periodBegin;
|
|
|
|
/**
|
|
* The date the invoice period begins.
|
|
* @var DateTime
|
|
*/
|
|
private $periodEnd;
|
|
|
|
/**
|
|
* The biannual fee rate - same as the cost, but not pro-rated for new timebanks.
|
|
* @var int
|
|
*/
|
|
private $biannualFeeRate;
|
|
|
|
/**
|
|
* An array of all the contact IDs for whom a contribution already exists in this billing period.
|
|
* @var array
|
|
*/
|
|
public static $contributionExists = [];
|
|
|
|
/**
|
|
* A string that represents the current billing period - e.g. "2019-2".
|
|
* First number is the year; second is 1 for Jan-Jun, 2 for Jul-Dec.
|
|
* @var string
|
|
*/
|
|
public static $billingPeriod = NULL;
|
|
|
|
/**
|
|
* Class constructor.
|
|
*/
|
|
public function __construct($cid, $memberCount) {
|
|
$this->cid = $cid;
|
|
$this->memberCount = $memberCount;
|
|
}
|
|
|
|
/**
|
|
* Generate invoices (called from API).
|
|
* @param array $params An array containing all the values passed into the Invoicegen.generate API.
|
|
* @return array APIv3 standard response.
|
|
*/
|
|
public static function generate($params) {
|
|
$cid = $billingPeriod = NULL;
|
|
if (isset($params['contact_id'])) {
|
|
$cid = $params['contact_id'];
|
|
}
|
|
if (isset($params['billing_period'])) {
|
|
$billingPeriod = $params['billing_period'];
|
|
}
|
|
self::setBillingPeriod($billingPeriod);
|
|
// Get a list of contact IDs for everyone to generate an invoice for.
|
|
$contacts = civicrm_api3('Contact', 'get', [
|
|
'return' => ["id", self::BILLABLEMEMBERFIELD, self::TBCREATED, self::NUMBEROFMEMBERSOFFSET],
|
|
'contact_id' => $cid,
|
|
'contact_type' => 'Organization',
|
|
self::TBBILLABLEFIELD => 1,
|
|
'options' => ['limit' => 0],
|
|
])['values'];
|
|
self::setContributionExists();
|
|
foreach ($contacts as $k => $contact) {
|
|
if (!in_array($k, self::$contributionExists)) {
|
|
$tb = new CRM_Tbusainvoicegen_Timebank($k, $contact[self::BILLABLEMEMBERFIELD]);
|
|
// Don't generate an invoice if there's no "TB Created" date.
|
|
if (!$contact[self::TBCREATED]) {
|
|
continue;
|
|
}
|
|
$tb->setMemberCountBillable($contact[self::NUMBEROFMEMBERSOFFSET]);
|
|
$tb->creationDate = new DateTime($contact[self::TBCREATED]);
|
|
$tb->setInvoiceDate($params);
|
|
$tb->setDueDate($params);
|
|
$tb->setPeriodBeginEndDate($params);
|
|
$tb->setPrice();
|
|
$tb->createContribution();
|
|
}
|
|
}
|
|
}
|
|
|
|
private function setMemberCountBillable($numberOfMembersOffset) {
|
|
// Default offset is 2.
|
|
$numberOfMembersOffset = $numberOfMembersOffset ?: 2;
|
|
$this->memberCountBillable = $this->memberCount - $numberOfMembersOffset;
|
|
if ($this->memberCountBillable < 0) {
|
|
$this->memberCountBillable = 0;
|
|
}
|
|
}
|
|
|
|
public static function setBillingPeriod($billingPeriod) {
|
|
if ($billingPeriod) {
|
|
self::$billingPeriod = $billingPeriod;
|
|
}
|
|
else {
|
|
// Should we try to auto-calculate it here? Or nah?
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate an array of contacts who already have a contribution created in this billing period.
|
|
*/
|
|
public static function setContributionExists() {
|
|
$existing = [];
|
|
$result = civicrm_api3('Contribution', 'get', [
|
|
self::BILLINGPERIODFIELD => self::$billingPeriod,
|
|
'options' => ['limit' => 0],
|
|
]);
|
|
if ($result['count']) {
|
|
foreach ($result['values'] as $contrib) {
|
|
$existing[$contrib['contact_id']] = $contrib['contact_id'];
|
|
}
|
|
}
|
|
self::$contributionExists = $existing;
|
|
}
|
|
|
|
private function setPrice() {
|
|
if ($this->price) {
|
|
return $this->price;
|
|
}
|
|
$adjustedMemberCount = $this->memberCountBillable;
|
|
foreach ($this->priceArray as $members => $cost) {
|
|
if ($adjustedMemberCount <= $members) {
|
|
break;
|
|
// After this "break", $cost will be accurate.
|
|
}
|
|
}
|
|
$this->biannualFeeRate = $cost;
|
|
// Pro-rate the payment if the timebank is new enough.
|
|
$this->price = round($cost * $this->monthsProRated() / 6, 2);
|
|
return $this->price;
|
|
}
|
|
|
|
/**
|
|
* Sets the due date
|
|
*/
|
|
private function setInvoiceDate($params = []) {
|
|
if (isset($params['invoice_date'])) {
|
|
$this->invoiceDate = new DateTime($params['invoice_date']);
|
|
}
|
|
else {
|
|
$this->invoiceDate = new DateTime();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If the first annual anniversary of the TB is before the billing period, then the TB pays for the full billing period (6 months).
|
|
* If the first annual anniversary of the TB is during any of the first 5 months of the billing period, then the TB pays a pro-rated fee for the number of full billing period months after the first annual anniversary of the TB (1 to 5 months).
|
|
* If the first annual anniversary of the TB is during the 6th month of the current billing OR after the billing period ends, then the TB pays $0 for the billing period.
|
|
*/
|
|
private function monthsProRated() {
|
|
// By default, we bill all 6 months in a billing period.
|
|
$monthsProRated = 6;
|
|
$beginDate = $this->periodBegin;
|
|
$firstAnniversary = $this->creationDate->modify("+1 year");
|
|
$interval = $beginDate->diff($firstAnniversary);
|
|
// First anniversary is before the billing period.
|
|
if ($interval->invert == 1) {
|
|
$monthsProRated = 6;
|
|
}
|
|
// Starts past the end of the billing period.
|
|
elseif ($interval->y >= 1 || $interval->m >= 6) {
|
|
$monthsProRated = 0;
|
|
}
|
|
// Falls within the billing period.
|
|
else {
|
|
$monthsProRated = 5 - $interval->m;
|
|
}
|
|
return $monthsProRated;
|
|
}
|
|
|
|
private function createContribution() {
|
|
if (!$this->price) {
|
|
return;
|
|
}
|
|
civicrm_api3('Contribution', 'create', [
|
|
'financial_type_id' => 'CW License Fee',
|
|
self::DUEDATE => $this->invoiceDueDate->format('Y-m-d'),
|
|
self::INVOICEDATE => $this->invoiceDate->format('Y-m-d'),
|
|
self::PERIODBEGIN => $this->periodBegin->format('Y-m-d'),
|
|
self::PERIODEND => $this->periodEnd->format('Y-m-d'),
|
|
self::BILLABLEMEMBERSCONTRIB => $this->memberCountBillable,
|
|
self::BIANNUALFEERATE => $this->biannualFeeRate,
|
|
'total_amount' => $this->price,
|
|
'contact_id' => $this->cid,
|
|
'contribution_status_id' => 'Pending',
|
|
'is_pay_later' => 1,
|
|
self::BILLINGPERIODFIELD => self::$billingPeriod,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Sets the due date
|
|
*/
|
|
private function setDueDate($params = []) {
|
|
if (isset($params['due_date'])) {
|
|
$this->invoiceDueDate = new DateTime($params['due_date']);
|
|
}
|
|
else {
|
|
list($year, $number) = explode('-', self::$billingPeriod);
|
|
if ($number == 1) {
|
|
$this->invoiceDueDate = new DateTime($year . '-03-31');
|
|
}
|
|
else {
|
|
$this->invoiceDueDate = new DateTime($year . '-09-30');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the period begin/end date.
|
|
*/
|
|
private function setPeriodBeginEndDate($params) {
|
|
if (isset($params['period_begin_date'])) {
|
|
$this->periodBegin = new DateTime($params['period_begin_date']);
|
|
}
|
|
else {
|
|
list($year, $number) = explode('-', self::$billingPeriod);
|
|
if ($number == 1) {
|
|
$this->periodBegin = new DateTime($year . '-01-01');
|
|
}
|
|
else {
|
|
$this->periodBegin = new DateTime($year . '-07-01');
|
|
}
|
|
}
|
|
if (isset($params['period_end_date'])) {
|
|
$this->periodEnd = new DateTime($params['period_end_date']);
|
|
}
|
|
else {
|
|
list($year, $number) = explode('-', self::$billingPeriod);
|
|
if ($number == 1) {
|
|
$this->periodEnd = new DateTime($year . '-06-30');
|
|
}
|
|
else {
|
|
$this->periodEnd = new DateTime($year . '-12-31');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is a helper function to generate values for the custom invoice template.
|
|
* It takes a billing period (e.g. "2019-2") and returns an array $invoiceData.
|
|
* $invoiceData[0] is the due date (e.g. "9/30/2019)
|
|
* $invoiceData[1] is the billing period (e.g. "7/1/2019-12/31/2019").
|
|
* $invoiceData[2] is the total amount this contact owes.
|
|
* @param int $contactId the contact ID of the timebank.
|
|
* @param str $billingPeriod
|
|
*/
|
|
public static function invoiceData($contactId, $billingPeriod) {
|
|
self::setBillingPeriod($billingPeriod);
|
|
$invoiceData[2] = self::calculateTotalDue($contactId);
|
|
return $invoiceData;
|
|
}
|
|
|
|
public static function calculateTotalDue($contactId) {
|
|
// Get all contributions that aren't paid, sum up their total amounts.
|
|
// Then get all those contributions' payments, sum up THEIR total amounts.
|
|
// Subtract the second number from the first.
|
|
$totalDue = 0;
|
|
$contributions = civicrm_api3('Contribution', 'get', [
|
|
'sequential' => 1,
|
|
'return' => ["total_amount"],
|
|
'contact_id' => $contactId,
|
|
'contribution_status_id' => ['!=' => "Completed"],
|
|
'options' => ['limit' => 0],
|
|
]);
|
|
if ($contributions['count']) {
|
|
foreach ($contributions['values'] as $contribution) {
|
|
$totalDue += $contribution['total_amount'];
|
|
$contributionIds[] = $contribution['id'];
|
|
}
|
|
}
|
|
$payments = civicrm_api3('Payment', 'get', [
|
|
'sequential' => 1,
|
|
'contribution_id' => ['IN' => $contributionIds],
|
|
'options' => ['limit' => 0],
|
|
]);
|
|
if ($payments['count']) {
|
|
foreach ($payments['values'] as $payment) {
|
|
$totalDue -= $payment['total_amount'];
|
|
}
|
|
}
|
|
return round($totalDue, 2);
|
|
}
|
|
|
|
}
|