359 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			359 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
class CRM_Tbusainvoicegen_Timebank {
 | 
						|
 | 
						|
  // Live settings
 | 
						|
  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';
 | 
						|
 | 
						|
  // Local dev settings.
 | 
						|
//  const TBBILLABLEFIELD = 'custom_14';
 | 
						|
//  const BILLABLEMEMBERFIELD = 'custom_15';
 | 
						|
//  const BILLINGPERIODFIELD = 'custom_7';
 | 
						|
//  const TBCREATED = 'custom_16';
 | 
						|
//  const INVOICEDATE = 'custom_9';
 | 
						|
//  const DUEDATE = 'custom_11';
 | 
						|
//  const PERIODBEGIN = 'custom_12';
 | 
						|
//  const PERIODEND = 'custom_13';
 | 
						|
//  const BILLABLEMEMBERSCONTRIB = 'custom_8';
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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;
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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;
 | 
						|
    $this->memberCountBillable = $memberCount - 2;
 | 
						|
    if ($this->memberCountBillable < 0) {
 | 
						|
      $this->memberCountBillable = 0;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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],
 | 
						|
      '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->creationDate = new DateTime($contact[self::TBCREATED]);
 | 
						|
        $tb->setInvoiceDate($params);
 | 
						|
        $tb->setDueDate($params);
 | 
						|
        $tb->setPeriodBeginEndDate($params);
 | 
						|
        $tb->setPrice();
 | 
						|
        $tb->createContribution();
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  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,
 | 
						|
    ]);
 | 
						|
    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.
 | 
						|
      }
 | 
						|
    }
 | 
						|
    // 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,
 | 
						|
      '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 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);
 | 
						|
  }
 | 
						|
 | 
						|
}
 |