Created
November 26, 2015 17:13
-
-
Save antoniovassell/39cc2b6bb33d4f5f683d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Model\Table; | |
use App\Model\Entity\TransactionItem; | |
use ArrayObject; | |
use Cake\Core\Configure; | |
use Cake\Error\Debugger; | |
use Cake\Event\Event; | |
use Cake\ORM\Entity; | |
use Cake\ORM\Query; | |
use Cake\ORM\RulesChecker; | |
use Cake\ORM\Table; | |
use Cake\ORM\TableRegistry; | |
use Cake\Validation\Validator; | |
use App\Model\Entity\ForeignExchangeTrait; | |
use Doctrine\Instantiator\Exception\InvalidArgumentException; | |
use Assert\Assertion; | |
/** | |
* TransactionItems Model | |
* | |
* @property \App\Model\Table\TransactionFrequenciesTable $TransactionFrequencies | |
*/ | |
class TransactionItemsTable extends Table | |
{ | |
use ForeignExchangeTrait; | |
/** | |
* Initialize method | |
* | |
* @param array $config The configuration for the Table. | |
* @return void | |
*/ | |
public function initialize(array $config) | |
{ | |
$this->table('transaction_items'); | |
$this->displayField('name'); | |
$this->primaryKey('id'); | |
$this->addBehavior('Timestamp'); | |
$this->belongsTo('Users', [ | |
'foreignKey' => 'user_id' | |
]); | |
$this->belongsTo('TransactionTypes', [ | |
'foreignKey' => 'transaction_type_id' | |
]); | |
$this->belongsTo('TransactionFrequencies', [ | |
'foreignKey' => 'transaction_frequency_id' | |
]); | |
$this->belongsTo('Accounts', [ | |
'foreignKey' => 'account_id' | |
]); | |
$this->hasMany('Transactions', [ | |
'foreignKey' => 'transaction_item_id' | |
]); | |
$this->belongsTo('TransactionCategories', [ | |
'foreignKey' => 'transaction_category_id' | |
]); | |
} | |
/** | |
* Default validation rules. | |
* | |
* @param \Cake\Validation\Validator $validator Validator instance. | |
* @return \Cake\Validation\Validator | |
*/ | |
public function validationDefault(Validator $validator) | |
{ | |
$validator | |
->add('id', 'valid', ['rule' => 'uuid']) | |
->allowEmpty('id', 'create') | |
->requirePresence('name', 'create') | |
->notEmpty('name') | |
->add('priority', 'valid', ['rule' => 'numeric']) | |
->notEmpty('priority') | |
->add('user_id', 'valid', ['rule' => 'uuid']) | |
->requirePresence('user_id', 'create') | |
->notEmpty('user_id') | |
->add('account_id', 'valid', ['rule' => 'uuid']) | |
->requirePresence('account_id', 'create') | |
->notEmpty('account_id') | |
->add('transaction_type_id', 'valid', ['rule' => 'uuid']) | |
->requirePresence('transaction_type_id', 'create') | |
->notEmpty('transaction_type_id') | |
->add('transaction_frequency_id', 'valid', ['rule' => 'uuid']) | |
->requirePresence('transaction_frequency_id', function ($context) { | |
return (isset($context['data']['is_recurrent']) && $context['data']['is_recurrent'] === true); | |
}) | |
->notEmpty('transaction_frequency_id') | |
->add('value', 'valid', ['rule' => 'decimal']) | |
->requirePresence('value', 'create') | |
->notEmpty('value') | |
->add('input_value', 'valid', ['rule' => 'decimal']) | |
->requirePresence('input_value', 'create') | |
->notEmpty('input_value') | |
->add('is_recurrent', 'valid', ['rule' => 'boolean']) | |
->requirePresence('is_recurrent', 'create') | |
->notEmpty('is_recurrent') | |
->add('is_ongoing', 'valid', ['rule' => 'boolean']) | |
->requirePresence('is_ongoing', function ($context) { | |
return (isset($context['data']['is_recurrent']) && $context['data']['is_recurrent'] === true); | |
}) | |
->notEmpty('is_ongoing') | |
->add('date', 'valid', ['rule' => 'date']) | |
->requirePresence('date', 'create') | |
->notEmpty('date') | |
->add('end_date', 'valid', ['rule' => 'date']) | |
->add('end_date', 'greaterThanStartDate', [ | |
'rule' => function ($value, $context) { | |
return strtotime($context['data']['date']) <= strtotime($value); | |
} | |
]) | |
->requirePresence('end_date', function ($context) { | |
return (isset($context['data']['is_recurrent']) && | |
$context['data']['is_recurrent'] === true && | |
isset($context['data']['is_ongoing']) && | |
$context['data']['is_ongoing'] === false); | |
}) | |
->notEmpty('end_date', 'This field is required', function ($context) { | |
return (isset($context['data']['is_recurrent']) && $context['data']['is_recurrent'] === true); | |
}); | |
return $validator; | |
} | |
/** | |
* Returns a rules checker object that will be used for validating | |
* application integrity. | |
* | |
* @todo: Might need to validate currency as well | |
* @param \Cake\ORM\RulesChecker $rules The rules object to be modified. | |
* @return \Cake\ORM\RulesChecker | |
*/ | |
public function buildRules(RulesChecker $rules) | |
{ | |
$rules->add($rules->existsIn(['user_id'], 'Users')); | |
$rules->add($rules->existsIn(['transaction_type_id'], 'TransactionTypes')); | |
$rules->add($rules->existsIn(['transaction_frequency_id'], 'TransactionFrequencies')); | |
$rules->add($rules->existsIn(['account_id'], 'Accounts')); | |
$rules->add($rules->existsIn(['transaction_category_id'], 'TransactionCategories')); | |
return $rules; | |
} | |
/** | |
* Add new item | |
* | |
* @param Entity $postData Posted data | |
* @return array | |
*/ | |
public function add($postData) | |
{ | |
if (!empty($postData->errors())) { | |
throw new InvalidArgumentException(__('Transaction Item is missing data')); | |
} | |
// @todo: cache | |
$transactionTypes = TableRegistry::get('TransactionTypes')->find('list')->toArray(); | |
$postData->value = $this->getSignedValue( | |
$postData->value, | |
$transactionTypes, | |
$postData->transaction_type_id | |
); | |
$postData->input_value = $this->getSignedValue( | |
$postData->input_value, | |
$transactionTypes, | |
$postData->transaction_type_id | |
); | |
$transactionsData = $this->calculateTransactions($postData); | |
$postData->transactions = $this->Transactions->newEntities($transactionsData); | |
if (empty($postData->errors)) { | |
if ($this->save($postData)) { | |
$event = new Event('Model.Transaction.afterUpdate', $this, [ | |
'userId' => $postData->user_id | |
]); | |
$this->eventManager()->dispatch($event); | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Calculate individual transactions base on transaction item added | |
* | |
* @param Entity $postData | |
* @return array | |
*/ | |
public function calculateTransactions($postData) | |
{ | |
$transactionsData = []; | |
if ($postData->is_recurrent === true) { | |
$occurrences = $this->TransactionFrequencies->getOccurrences($postData); | |
} else { | |
$occurrences = [$postData->date]; | |
} | |
foreach ($occurrences as $index => $date) { | |
$transactionsData[] = [ | |
'account_id' => $postData->account_id, | |
'value' => $postData->value, | |
'processed' => false, | |
'priority' => ($postData->priority) ? $postData->priority : 1, | |
'date' => $date, | |
'currency' => $postData->currency, | |
'exchange_rate' => $postData->exchange_rate, | |
'exchange_rate_to_original' => $postData->exchange_rate_to_original, | |
'input_value' => $postData->input_value | |
]; | |
} | |
return $transactionsData; | |
} | |
/** | |
* Edit method | |
* | |
* @param $postData | |
* @return bool | |
*/ | |
public function edit($postData) | |
{ | |
$this->Transactions->deleteAll(['Transactions.transaction_item_id' => $postData->id]); | |
$this->add($postData); | |
return true; | |
} | |
/** | |
* Get signed value base on the type of transaction (income(+) or expense (-)) | |
* | |
* @todo: Income should always be positive, expense always negative | |
* @param float $value Monetary value | |
* @param array $transactionTypes Types of transactions (income, expenses, etc) | |
* @param UUID $transactionTypeId | |
* @return float Signed monetary value | |
*/ | |
public function getSignedValue($value, $transactionTypes, $transactionTypeId) | |
{ | |
$transactionType = null; | |
Assertion::notEmpty($transactionTypeId, __('Transaction Type ID cannot be empty')); | |
Assertion::keyIsset($transactionTypes, $transactionTypeId, __('Transaction Type not valid')); | |
$transactionType = $transactionTypes[$transactionTypeId]; | |
switch ($transactionType) { | |
case 'Income': | |
return $value; | |
case 'Expense': | |
return abs($value) * (-1); | |
} | |
return false; | |
} | |
/** | |
* Find transaction items, find method for pagination | |
* | |
* @param Query $query | |
* @param array $options | |
* @return Query | |
*/ | |
public function findTransactionItems(Query $query, array $options) | |
{ | |
$conditions = [ | |
'TransactionItems.user_id' => $options['options']['user_id'], | |
'Transactions.date >=' => $options['options']['start_date'], | |
'Transactions.date <=' => $options['options']['end_date'] | |
]; | |
$query->find('all') | |
->where($conditions) | |
->contain(['TransactionItems']) | |
->order(['Transactions.priority', 'Transactions.date']); | |
return $query; | |
} | |
/** | |
* Before marshal callback | |
* | |
* @param Event $event | |
* @param ArrayObject $data | |
* @param ArrayObject $options | |
*/ | |
public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options) | |
{ | |
if (isset($data['date']) && !empty($data['date'])) { | |
$data['date'] = date('Y-m-d', strtotime($data['date'])); | |
} | |
if (isset($data['end_date']) && !empty($data['end_date'])) { | |
$data['end_date'] = date('Y-m-d', strtotime($data['end_date'])); | |
} | |
$data = $this->setFxValues($data); | |
} | |
/** | |
* Get transaction items per month given a user id | |
* | |
* @param UUID $userId Id of user | |
* @return \Cake\Datasource\ResultSetInterface | |
*/ | |
public function getItemsPerMonth($userId) | |
{ | |
$query = $this->find(); | |
$processedTransactions = $query->newExpr()->addCase($query->newExpr()->add(['Transactions.processed' => true]), '`value`', 'integer'); | |
$query | |
->where(['TransactionItems.user_id' => $userId]) | |
->select([ | |
'TransactionItems.id', | |
'TransactionItems.name', | |
'TransactionItems.value', | |
'TransactionItems.input_value', | |
'TransactionItems.currency', | |
'TransactionItems.account_id', | |
'TransactionItems.transaction_type_id', | |
'TransactionItems.transaction_frequency_id', | |
'month_year' => 'CONCAT(MONTHNAME(Transactions.date), ", ", YEAR(Transactions.date))', | |
'year' => 'YEAR(Transactions.date)', | |
'transactions_sum' => $query->func()->sum('Transactions.value'), | |
'transactions_done_sum' => $query->func()->sum($processedTransactions), | |
'transactions_count' => $query->func()->count('Transactions.id') | |
]) | |
->leftJoinWith('Transactions') | |
->hydrate(false) | |
->group(['YEAR(Transactions.date)', 'MONTH(Transactions.date)', 'TransactionItems.id']); | |
$result = $query->all(); | |
return $result; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment