array('numeric'), 'name' => array('notempty'), ); var $belongsTo = array( 'PrimaryContact' => array( 'className' => 'Contact', ), ); var $hasMany = array( 'CurrentLease' => array( 'className' => 'Lease', 'conditions' => 'CurrentLease.close_date IS NULL', ), 'Lease', 'StatementEntry', 'ContactsCustomer' => array( // It would be nice to claim a dependency here, which would // simplify deletion of a customer. However, for this to work // Cake must have a primaryKey as a single field. This table // makes use of a complex key, so we're out of luck. /* 'dependent' => true, */ ), 'Transaction', 'Tender', ); var $hasAndBelongsToMany = array( 'Contact' => array( 'unique' => true, ), ); //var $default_log_level = 20; /************************************************************************** ************************************************************************** ************************************************************************** * function: accountId * - Returns the account ID for the given customer */ function accountId($id) { $this->cacheQueries = true; $customer = $this->find('first', array('contain' => false, 'fields' => array('account_id'), 'conditions' => array(array('Customer.id' => $id)))); $this->cacheQueries = false; return $customer['Customer']['account_id']; } /************************************************************************** ************************************************************************** ************************************************************************** * function: leaseIds * - Returns the lease IDs for the given customer */ function leaseIds($id, $current = false) { $Lease = $current ? 'CurrentLease' : 'Lease'; $this->cacheQueries = true; $customer = $this->find('first', array('contain' => array($Lease => array('fields' => array('id'))), 'fields' => array(), 'conditions' => array(array('Customer.id' => $id)))); $this->cacheQueries = false; $ids = array(); foreach ($customer[$Lease] AS $lease) $ids[] = $lease['id']; return $ids; } /************************************************************************** ************************************************************************** ************************************************************************** * function: securityDeposits * - Returns an array of security deposit entries */ function securityDeposits($id, $query = null) { $this->prEnter(compact('id', 'query')); $this->queryInit($query); $query['conditions'][] = array('StatementEntry.customer_id' => $id); $query['conditions'][] = array('StatementEntry.account_id' => $this->StatementEntry->Account->securityDepositAccountID()); $set = $this->StatementEntry->reconciledSet('CHARGE', $query, false, true); return $this->prReturn($set); } /************************************************************************** ************************************************************************** ************************************************************************** * function: securityDepositBalance * - Returns the balance of the customer security deposit(s) */ function securityDepositBalance($id, $query = null) { $this->prEnter(compact('id', 'query')); $this->queryInit($query); $sd_account_id = $this->StatementEntry->Account->securityDepositAccountID(); $squery = $query; $squery['conditions'][] = array('StatementEntry.customer_id' => $id); $squery['conditions'][] = array('StatementEntry.account_id' => $sd_account_id); $stats = $this->StatementEntry->stats(null, $squery); $this->pr(26, compact('squery', 'stats')); // OK, we know now how much we charged for a security // deposit, as well as how much we received to pay for it. // Now we need to know if any has been released. // Yes... this sucks. $lquery = $query; $lquery['link'] = array('Transaction' => array('fields' => array(), 'Customer' => (empty($query['link']) ? array('fields' => array()) : $query['link']))); $lquery['conditions'][] = array('Transaction.customer_id' => $id); $lquery['conditions'][] = array('LedgerEntry.account_id' => $sd_account_id); $lquery['conditions'][] = array('LedgerEntry.crdr' => 'DEBIT'); $lquery['fields'][] = 'SUM(LedgerEntry.amount) AS total'; $released = $this->StatementEntry->Transaction->LedgerEntry->find ('first', $lquery); $this->pr(26, compact('lquery', 'released')); return $this->prReturn($stats['Charge']['disbursement'] - $released[0]['total']); } /************************************************************************** ************************************************************************** ************************************************************************** * function: unreconciledCharges * - Returns charges have not yet been fully paid */ function unreconciledCharges($id, $query = null) { $this->prEnter(compact('id', 'query')); $this->queryInit($query); $query['conditions'][] = array('StatementEntry.customer_id' => $id); $set = $this->StatementEntry->reconciledSet('CHARGE', $query, true); return $this->prReturn($set); } /************************************************************************** ************************************************************************** ************************************************************************** * function: saveCustomer * - Saves the customer and related data */ function saveCustomer($id, $data, $primary_contact_entry) { // Go through each contact, and create new ones as needed foreach ($data['Contact'] AS &$contact) { if (isset($contact['id'])) continue; $I = new Contact(); if (!$I->saveContact(null, array('Contact' => $contact))) return false; $contact['id'] = $I->id; } // Set the primary contact ID based on caller selection $data['Customer']['primary_contact_id'] = $data['Contact'][$primary_contact_entry]['id']; // Provide a default customer name if not specified if (!$data['Customer']['name']) { $this->Contact->recursive = -1; $pcontact = $this->Contact->read(null, $data['Customer']['primary_contact_id']); $data['Customer']['name'] = $pcontact['Contact']['display_name']; } // Save the customer data $this->create(); if ($id) $this->id = $id; if (!$this->save($data, false)) { return false; } $id = $this->id; // Appears that $this->save() "helpfully" choses to add in // any missing data fields, populated with default values. // So, after saving is complete, the fields 'lease_count', // 'past_lease_count', and 'current_lease_count' have all // been reset to zero. Gee, thanks Cake... $this->update($id); // Remove all associated Customer Contacts, as it ensures // any entries deleted by the user actually get deleted // in the system. We'll recreate the needed ones anyway. // REVISIT : 20090706 // Appears that $this->save() is already doing the // delete. I would have thought this would only happen // on a saveAll?? /* $this->ContactsCustomer->deleteAll */ /* (array('customer_id' => $id), false); */ // At this point, since we've saved data to customer, // we'll proceed forward as much as possible, even // if we encounter an error. For now, we'll assume // the operation will succeed. $ret = true; // Go through each entry of this customer method foreach ($data['Contact'] AS &$contact) { // Update the ContactsCustomer to reflect the appropriate IDs $contact['ContactsCustomer']['customer_id'] = $id; $contact['ContactsCustomer']['contact_id'] = $contact['id']; // Save the relationship between customer and contact $CM = new ContactsCustomer(); if (!$CM->save($contact['ContactsCustomer'], false)) { $ret = false; } } // Return the result return $ret; } /************************************************************************** ************************************************************************** ************************************************************************** * function: update * - Update any cached or calculated fields */ function update($id) { $this->prEnter(compact('id')); if (empty($id)) { $customers = $this->find('all', array('contain' => false, 'fields' => array('id'))); foreach ($customers AS $customer) { // This SHOULDN'T happen, but check to be sure // or we'll get infinite recursion. if (empty($customer['Customer']['id'])) continue; $this->update($customer['Customer']['id']); } return; } // updateLeaseCount is typically handled directly when needed. // However, this function is used to _ensure_ customer info is // current, so we're obligated to call it anyway. $this->updateLeaseCount($id); $current_leases = $this->find('all', // REVISIT : 20090816 // Do we need to update leases other than the current ones? // It may be necessary. For example, a non-current lease // can still be hit with an NSF item. In that case, it // could have stale data if we look only to current leases. //array('link' => array('CurrentLease' => array('type' => 'INNER')), array('link' => array('Lease' => array('type' => 'INNER')), 'conditions' => array('Customer.id' => $id))); foreach ($current_leases AS $lease) { if (!empty($lease['CurrentLease']['id'])) $this->Lease->update($lease['CurrentLease']['id']); if (!empty($lease['Lease']['id'])) $this->Lease->update($lease['Lease']['id']); } } /************************************************************************** ************************************************************************** ************************************************************************** * function: merge * - Merges two customers into one */ function merge($dst_id, $src_id, $contacts) { $this->prEnter(compact('dst_id', 'src_id', 'contacts')); // Get the entire list of destination customer contacts $dst_contacts = array(); $result = $this->find('all', array('link' => array('ContactsCustomer'), 'fields' => array('ContactsCustomer.contact_id', 'ContactsCustomer.type'), 'conditions' => array(array('id' => $dst_id, 'ContactsCustomer.active' => true)))); foreach ($result AS $contact) { $dst_contacts[$contact['ContactsCustomer']['contact_id']] = $contact['ContactsCustomer']; } $this->pr(17, compact('dst_contacts')); // Get the entire list of source customer contacts $src_contacts = array(); $result = $this->find('all', array('link' => array('ContactsCustomer'), 'fields' => array('ContactsCustomer.contact_id', 'ContactsCustomer.type'), 'conditions' => array(array('id' => $src_id, 'ContactsCustomer.active' => true)))); foreach ($result AS $contact) { $src_contacts[$contact['ContactsCustomer']['contact_id']] = $contact['ContactsCustomer']; } $this->pr(17, compact('src_contacts')); // Verify the contacts list are all valid source customer contacts foreach ($contacts AS $contact_id) { if (!array_key_exists($contact_id, $src_contacts)) return $this->prReturn(false); } // Remove any contacts which are already destination customer contacts $new_contacts = array_diff($contacts, array_keys($dst_contacts)); $all_contacts = array_merge($new_contacts, array_keys($dst_contacts)); $this->pr(17, compact('new_contacts', 'all_contacts')); // For now, we'll assume the operation will succeed. $ret = true; // Add each desired source customer contact to the destination customer foreach ($new_contacts AS $contact_id) { $CM = new ContactsCustomer(); if (!$CM->save(array('customer_id' => $dst_id) + $src_contacts[$contact_id], false)) { $ret = false; } } $this->Lease->updateAll (array('Lease.customer_id' => $dst_id), array('Lease.customer_id' => $src_id) ); $this->Tender->updateAll (array('Tender.customer_id' => $dst_id), array('Tender.customer_id' => $src_id) ); $this->StatementEntry->updateAll (array('StatementEntry.customer_id' => $dst_id), array('StatementEntry.customer_id' => $src_id) ); $this->Transaction->updateAll (array('Transaction.customer_id' => $dst_id), array('Transaction.customer_id' => $src_id) ); // Make sure our lease counts, etc are correct $this->update($dst_id); // Delete the old customer $this->pr(12, compact('src_id'), "Delete Customer"); $this->delete($src_id); // Delete all the orphaned customers foreach (array_diff(array_keys($src_contacts), $all_contacts) AS $contact_id) { // Delete un-used or duplicate contacts // REVISIT 20100702: // Not sure if we really want to do this. // On the one hand, they're probably really redundant, // and only clutter up the list of all contacts. On the // other hand, it destroys data, not only losing the // history, but making it difficult to recover if the // merge is a mistake. Additionally, we need to do // extra checking to ensure that the contact is not // in use by some other customer. // We need some sort of Contact.deleted field... $this->pr(12, compact('contact_id'), "Delete Contact"); $this->Contact->delete($contact_id); } // Finally, delete all customer contact relationships $this->ContactsCustomer->deleteAll (array('customer_id' => $src_id), false); // Return the result return $this->prReturn($ret); } /************************************************************************** ************************************************************************** ************************************************************************** * function: updateLeaseCount * - Updates the internal lease count */ function updateLeaseCount($id) { $this->id = $id; $lease_count = $this->find('count', array('link' => array('Lease' => array('type' => 'INNER')), 'conditions' => array('Customer.id' => $id))); $current_count = $this->find('count', array('link' => array('CurrentLease' => array('type' => 'INNER')), 'conditions' => array('Customer.id' => $id))); $this->saveField('lease_count', $lease_count); $this->saveField('current_lease_count', $current_count); $this->saveField('past_lease_count', $lease_count - $current_count); } /************************************************************************** ************************************************************************** ************************************************************************** * function: balance * - Returns the balance of money owed on the lease */ function balance($id) { $stats = $this->stats($id); return $stats['balance']; } /************************************************************************** ************************************************************************** ************************************************************************** * function: stats * - Returns summary data from the requested customer. */ function stats($id = null, $query = null) { //$this->prFunctionLevel(20); $this->prEnter(compact('id', 'query')); if (!$id) return $this->prExit(null); $this->queryInit($query); // REVISIT : 20090725 // We'll need to go directly to the statement entries if // transactions are not always associated with the customer. // This could happen if either we remove the customer_id // field from Transaction, or we allow multiple customers // to be part of the same transaction (essentially making // the Transaction.customer_id meaningless). /* $stats = $this->StatementEntry->find */ /* ('first', array */ /* ('contain' => false, */ /* 'fields' => $this->StatementEntry->chargeDisbursementFields(true), */ /* 'conditions' => array('StatementEntry.customer_id' => $id), */ /* )); */ $find_stats = $this->StatementEntry->find ('first', array ('contain' => false, 'fields' => $this->StatementEntry->chargeDisbursementFields(true), 'conditions' => array('StatementEntry.customer_id' => $id), )); $find_stats = $find_stats[0]; $this->pr(17, compact('find_stats')); $tquery = $query; $tquery['conditions'][] = array('StatementEntry.customer_id' => $id); $statement_stats = $this->StatementEntry->stats(null, $tquery); $statement_stats['balance'] = $statement_stats['Charge']['balance']; $this->pr(17, compact('statement_stats')); $tquery = $query; //$tquery['conditions'][] = array('StatementEntry.customer_id' => $id); $tquery['conditions'][] = array('Transaction.customer_id' => $id); $transaction_stats = $this->Transaction->stats(null, $tquery); $transaction_stats += $transaction_stats['StatementEntry']; $this->pr(17, compact('transaction_stats')); $tquery = $query; //$tquery['conditions'][] = array('StatementEntry.customer_id' => $id); $tquery['conditions'][] = array('Transaction.customer_id' => $id); $ar_transaction_stats = $this->Transaction->stats(null, $tquery, $this->Transaction->Account->accountReceivableAccountID()); $ar_transaction_stats += $ar_transaction_stats['LedgerEntry']; $this->pr(17, compact('ar_transaction_stats')); //$stats = $ar_transaction_stats; $stats = $find_stats; return $this->prReturn($stats); } } ?>