Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2892773
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
27 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/resources/sql/autopatches/20141007.fundtotal.sql b/resources/sql/autopatches/20141007.fundtotal.sql
new file mode 100644
index 0000000000..d9b2d2714c
--- /dev/null
+++ b/resources/sql/autopatches/20141007.fundtotal.sql
@@ -0,0 +1,4 @@
+ALTER TABLE {$NAMESPACE}_fund.fund_initiative
+ ADD totalAsCurrency VARCHAR(64) NOT NULL COLLATE utf8_bin;
+
+UPDATE {$NAMESPACE}_fund.fund_initiative SET totalAsCurrency = '0.00 USD';
diff --git a/src/applications/fund/controller/FundInitiativeViewController.php b/src/applications/fund/controller/FundInitiativeViewController.php
index ad2f8bf471..2e5c5ca024 100644
--- a/src/applications/fund/controller/FundInitiativeViewController.php
+++ b/src/applications/fund/controller/FundInitiativeViewController.php
@@ -1,163 +1,176 @@
<?php
final class FundInitiativeViewController
extends FundController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$initiative = id(new FundInitiativeQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->executeOne();
if (!$initiative) {
return new Aphront404Response();
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($initiative->getMonogram());
$title = pht(
'%s %s',
$initiative->getMonogram(),
$initiative->getName());
if ($initiative->isClosed()) {
$status_icon = 'fa-times';
$status_color = 'bluegrey';
} else {
$status_icon = 'fa-check';
$status_color = 'bluegrey';
}
$status_name = idx(
FundInitiative::getStatusNameMap(),
$initiative->getStatus());
$header = id(new PHUIHeaderView())
->setObjectName($initiative->getMonogram())
->setHeader($initiative->getName())
->setUser($viewer)
->setPolicyObject($initiative)
->setStatus($status_icon, $status_color, $status_name);
$properties = $this->buildPropertyListView($initiative);
$actions = $this->buildActionListView($initiative);
$properties->setActionList($actions);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($properties);
$xactions = id(new FundInitiativeTransactionQuery())
->setViewer($viewer)
->withObjectPHIDs(array($initiative->getPHID()))
->execute();
$timeline = id(new PhabricatorApplicationTransactionView())
->setUser($viewer)
->setObjectPHID($initiative->getPHID())
->setTransactions($xactions);
return $this->buildApplicationPage(
array(
$crumbs,
$box,
$timeline,
),
array(
'title' => $title,
));
}
private function buildPropertyListView(FundInitiative $initiative) {
$viewer = $this->getRequest()->getUser();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($initiative);
$owner_phid = $initiative->getOwnerPHID();
- $this->loadHandles(array($owner_phid));
+ $merchant_phid = $initiative->getMerchantPHID();
+ $this->loadHandles(
+ array(
+ $owner_phid,
+ $merchant_phid,
+ ));
$view->addProperty(
pht('Owner'),
$this->getHandle($owner_phid)->renderLink());
+ $view->addProperty(
+ pht('Payable To Merchant'),
+ $this->getHandle($merchant_phid)->renderLink());
+
+ $view->addProperty(
+ pht('Total Funding'),
+ $initiative->getTotalAsCurrency()->formatForDisplay());
+
$view->invokeWillRenderEvent();
$description = $initiative->getDescription();
if (strlen($description)) {
$description = PhabricatorMarkupEngine::renderOneObject(
id(new PhabricatorMarkupOneOff())->setContent($description),
'default',
$viewer);
$view->addSectionHeader(pht('Description'));
$view->addTextContent($description);
}
return $view;
}
private function buildActionListView(FundInitiative $initiative) {
$viewer = $this->getRequest()->getUser();
$id = $initiative->getID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$initiative,
PhabricatorPolicyCapability::CAN_EDIT);
$view = id(new PhabricatorActionListView())
->setUser($viewer)
->setObject($initiative);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Initiative'))
->setIcon('fa-pencil')
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit)
->setHref($this->getApplicationURI("/edit/{$id}/")));
if ($initiative->isClosed()) {
$close_name = pht('Reopen Initiative');
$close_icon = 'fa-check';
} else {
$close_name = pht('Close Initiative');
$close_icon = 'fa-times';
}
$view->addAction(
id(new PhabricatorActionView())
->setName($close_name)
->setIcon($close_icon)
->setDisabled(!$can_edit)
->setWorkflow(true)
->setHref($this->getApplicationURI("/close/{$id}/")));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Back Initiative'))
->setIcon('fa-money')
->setDisabled($initiative->isClosed())
->setWorkflow(true)
->setHref($this->getApplicationURI("/back/{$id}/")));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('View Backers'))
->setIcon('fa-bank')
->setHref($this->getApplicationURI("/backers/{$id}/")));
return $view;
}
}
diff --git a/src/applications/fund/editor/FundInitiativeEditor.php b/src/applications/fund/editor/FundInitiativeEditor.php
index 39df92e51e..2bbf311cc6 100644
--- a/src/applications/fund/editor/FundInitiativeEditor.php
+++ b/src/applications/fund/editor/FundInitiativeEditor.php
@@ -1,181 +1,192 @@
<?php
final class FundInitiativeEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorFundApplication';
}
public function getEditorObjectsDescription() {
return pht('Fund Initiatives');
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = FundInitiativeTransaction::TYPE_NAME;
$types[] = FundInitiativeTransaction::TYPE_DESCRIPTION;
$types[] = FundInitiativeTransaction::TYPE_STATUS;
$types[] = FundInitiativeTransaction::TYPE_BACKER;
$types[] = FundInitiativeTransaction::TYPE_MERCHANT;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case FundInitiativeTransaction::TYPE_NAME:
return $object->getName();
case FundInitiativeTransaction::TYPE_DESCRIPTION:
return $object->getDescription();
case FundInitiativeTransaction::TYPE_STATUS:
return $object->getStatus();
case FundInitiativeTransaction::TYPE_BACKER:
return null;
case FundInitiativeTransaction::TYPE_MERCHANT:
return $object->getMerchantPHID();
}
return parent::getCustomTransactionOldValue($object, $xaction);
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case FundInitiativeTransaction::TYPE_NAME:
case FundInitiativeTransaction::TYPE_DESCRIPTION:
case FundInitiativeTransaction::TYPE_STATUS:
case FundInitiativeTransaction::TYPE_BACKER:
case FundInitiativeTransaction::TYPE_MERCHANT:
return $xaction->getNewValue();
}
return parent::getCustomTransactionNewValue($object, $xaction);
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case FundInitiativeTransaction::TYPE_NAME:
$object->setName($xaction->getNewValue());
return;
case FundInitiativeTransaction::TYPE_DESCRIPTION:
$object->setDescription($xaction->getNewValue());
return;
case FundInitiativeTransaction::TYPE_MERCHANT:
$object->setMerchantPHID($xaction->getNewValue());
return;
case FundInitiativeTransaction::TYPE_STATUS:
$object->setStatus($xaction->getNewValue());
return;
case FundInitiativeTransaction::TYPE_BACKER:
- // TODO: Calculate total funding / backers / etc.
+ $backer = id(new FundBackerQuery())
+ ->setViewer($this->requireActor())
+ ->withPHIDs(array($xaction->getNewValue()))
+ ->executeOne();
+ if (!$backer) {
+ throw new Exception(pht('No such backer!'));
+ }
+
+ $backer_amount = $backer->getAmountAsCurrency();
+ $total = $object->getTotalAsCurrency()->add($backer_amount);
+ $object->setTotalAsCurrency($total);
+
return;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
case PhabricatorTransactions::TYPE_EDGE:
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case FundInitiativeTransaction::TYPE_NAME:
case FundInitiativeTransaction::TYPE_DESCRIPTION:
case FundInitiativeTransaction::TYPE_STATUS:
case FundInitiativeTransaction::TYPE_MERCHANT:
case FundInitiativeTransaction::TYPE_BACKER:
// TODO: Maybe we should apply the backer transaction from here?
return;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
case PhabricatorTransactions::TYPE_EDGE:
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
case FundInitiativeTransaction::TYPE_NAME:
$missing = $this->validateIsEmptyTextField(
$object->getName(),
$xactions);
if ($missing) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('Initiative name is required.'),
nonempty(last($xactions), null));
$error->setIsMissingFieldError(true);
$errors[] = $error;
}
break;
case FundInitiativeTransaction::TYPE_MERCHANT:
$missing = $this->validateIsEmptyTextField(
$object->getName(),
$xactions);
if ($missing) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('Payable merchant is required.'),
nonempty(last($xactions), null));
$error->setIsMissingFieldError(true);
$errors[] = $error;
} else if ($xactions) {
$merchant_phid = last($xactions)->getNewValue();
// Make sure the actor has permission to edit the merchant they're
// selecting. You aren't allowed to send payments to an account you
// do not control.
$merchants = id(new PhortuneMerchantQuery())
->setViewer($this->requireActor())
->withPHIDs(array($merchant_phid))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->execute();
if (!$merchants) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'You must specify a merchant account you control as the '.
'recipient of funds from this initiative.'),
last($xactions));
$errors[] = $error;
}
}
break;
}
return $errors;
}
}
diff --git a/src/applications/fund/storage/FundInitiative.php b/src/applications/fund/storage/FundInitiative.php
index edf3d6407a..f9ed49eab1 100644
--- a/src/applications/fund/storage/FundInitiative.php
+++ b/src/applications/fund/storage/FundInitiative.php
@@ -1,173 +1,179 @@
<?php
final class FundInitiative extends FundDAO
implements
PhabricatorPolicyInterface,
PhabricatorProjectInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorSubscribableInterface,
PhabricatorMentionableInterface,
PhabricatorFlaggableInterface,
PhabricatorTokenReceiverInterface,
PhabricatorDestructibleInterface {
protected $name;
protected $ownerPHID;
protected $merchantPHID;
protected $description;
protected $viewPolicy;
protected $editPolicy;
protected $status;
+ protected $totalAsCurrency;
private $projectPHIDs = self::ATTACHABLE;
const STATUS_OPEN = 'open';
const STATUS_CLOSED = 'closed';
public static function getStatusNameMap() {
return array(
self::STATUS_OPEN => pht('Open'),
self::STATUS_CLOSED => pht('Closed'),
);
}
public static function initializeNewInitiative(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
->withClasses(array('PhabricatorFundApplication'))
->executeOne();
$view_policy = $app->getPolicy(FundDefaultViewCapability::CAPABILITY);
return id(new FundInitiative())
->setOwnerPHID($actor->getPHID())
->setViewPolicy($view_policy)
->setEditPolicy($actor->getPHID())
- ->setStatus(self::STATUS_OPEN);
+ ->setStatus(self::STATUS_OPEN)
+ ->setTotalAsCurrency(PhortuneCurrency::newEmptyCurrency());
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text255',
'description' => 'text',
'status' => 'text32',
'merchantPHID' => 'phid?',
+ 'totalAsCurrency' => 'text64',
+ ),
+ self::CONFIG_APPLICATION_SERIALIZERS => array(
+ 'totalAsCurrency' => new PhortuneCurrencySerializer(),
),
self::CONFIG_KEY_SCHEMA => array(
'key_status' => array(
'columns' => array('status'),
),
'key_owner' => array(
'columns' => array('ownerPHID'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(FundInitiativePHIDType::TYPECONST);
}
public function getMonogram() {
return 'I'.$this->getID();
}
public function getProjectPHIDs() {
return $this->assertAttached($this->projectPHIDs);
}
public function attachProjectPHIDs(array $phids) {
$this->projectPHIDs = $phids;
return $this;
}
public function isClosed() {
return ($this->getStatus() == self::STATUS_CLOSED);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return ($viewer->getPHID() == $this->getOwnerPHID());
}
public function describeAutomaticCapability($capability) {
return pht(
'The owner of an initiative can always view and edit it.');
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new FundInitiativeEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new FundInitiativeTransaction();
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
return ($phid == $this->getOwnerPHID());
}
public function shouldShowSubscribersProperty() {
return true;
}
public function shouldAllowSubscription($phid) {
return true;
}
/* -( PhabricatorTokenRecevierInterface )---------------------------------- */
public function getUsersToNotifyOfTokenGiven() {
return array(
$this->getOwnerPHID(),
);
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$this->delete();
$this->saveTransaction();
}
}
diff --git a/src/applications/phortune/currency/PhortuneCurrency.php b/src/applications/phortune/currency/PhortuneCurrency.php
index 88b9833e4d..573532fb73 100644
--- a/src/applications/phortune/currency/PhortuneCurrency.php
+++ b/src/applications/phortune/currency/PhortuneCurrency.php
@@ -1,185 +1,193 @@
<?php
final class PhortuneCurrency extends Phobject {
private $value;
private $currency;
private function __construct() {
// Intentionally private.
}
public static function getDefaultCurrency() {
return 'USD';
}
public static function newEmptyCurrency() {
return self::newFromString('0.00 USD');
}
public static function newFromUserInput(PhabricatorUser $user, $string) {
// Eventually, this might select a default currency based on user settings.
return self::newFromString($string, self::getDefaultCurrency());
}
public static function newFromString($string, $default = null) {
$matches = null;
$ok = preg_match(
'/^([-$]*(?:\d+)?(?:[.]\d{0,2})?)(?:\s+([A-Z]+))?$/',
trim($string),
$matches);
if (!$ok) {
self::throwFormatException($string);
}
$value = $matches[1];
if (substr_count($value, '-') > 1) {
self::throwFormatException($string);
}
if (substr_count($value, '$') > 1) {
self::throwFormatException($string);
}
$value = str_replace('$', '', $value);
$value = (float)$value;
$value = (int)round(100 * $value);
$currency = idx($matches, 2, $default);
if ($currency) {
switch ($currency) {
case 'USD':
break;
default:
throw new Exception("Unsupported currency '{$currency}'!");
}
}
return self::newFromValueAndCurrency($value, $currency);
}
public static function newFromValueAndCurrency($value, $currency) {
$obj = new PhortuneCurrency();
$obj->value = $value;
$obj->currency = $currency;
return $obj;
}
public static function newFromList(array $list) {
assert_instances_of($list, 'PhortuneCurrency');
- $total = 0;
- $currency = null;
+ if (!$list) {
+ return PhortuneCurrency::newEmptyCurrency();
+ }
+
+ $total = null;
foreach ($list as $item) {
- if ($currency === null) {
- $currency = $item->getCurrency();
- } else if ($currency === $item->getCurrency()) {
- // Adding a value denominated in the same currency, which is
- // fine.
+ if ($total === null) {
+ $total = $item;
} else {
- throw new Exception(
- pht('Trying to sum a list of unlike currencies.'));
+ $total = $total->add($item);
}
-
- // TODO: This should check for integer overflows, etc.
- $total += $item->getValue();
}
- return PhortuneCurrency::newFromValueAndCurrency(
- $total,
- self::getDefaultCurrency());
+ return $total;
}
public function formatForDisplay() {
$bare = $this->formatBareValue();
return '$'.$bare.' '.$this->currency;
}
public function serializeForStorage() {
return $this->formatBareValue().' '.$this->currency;
}
public function formatBareValue() {
switch ($this->currency) {
case 'USD':
return sprintf('%.02f', $this->value / 100);
default:
throw new Exception(
pht('Unsupported currency ("%s")!', $this->currency));
}
}
public function getValue() {
return $this->value;
}
public function getCurrency() {
return $this->currency;
}
public function getValueInUSDCents() {
if ($this->currency !== 'USD') {
throw new Exception(pht('Unexpected currency!'));
}
return $this->value;
}
private static function throwFormatException($string) {
throw new Exception("Invalid currency format ('{$string}').");
}
+ public function add(PhortuneCurrency $other) {
+ if ($this->currency !== $other->currency) {
+ throw new Exception(pht('Trying to add unlike currencies!'));
+ }
+
+ $currency = new PhortuneCurrency();
+
+ // TODO: This should check for integer overflows, etc.
+ $currency->value = $this->value + $other->value;
+ $currency->currency = $this->currency;
+
+ return $currency;
+ }
+
/**
* Assert that a currency value lies within a range.
*
* Throws if the value is not between the minimum and maximum, inclusive.
*
* In particular, currency values can be negative (to represent a debt or
* credit), so checking against zero may be useful to make sure a value
* has the expected sign.
*
* @param string|null Currency string, or null to skip check.
* @param string|null Currency string, or null to skip check.
* @return this
*/
public function assertInRange($minimum, $maximum) {
if ($minimum !== null && $maximum !== null) {
$min = PhortuneCurrency::newFromString($minimum);
$max = PhortuneCurrency::newFromString($maximum);
if ($min->value > $max->value) {
throw new Exception(
pht(
'Range (%s - %s) is not valid!',
$min->formatForDisplay(),
$max->formatForDisplay()));
}
}
if ($minimum !== null) {
$min = PhortuneCurrency::newFromString($minimum);
if ($min->value > $this->value) {
throw new Exception(
pht(
'Minimum allowed amount is %s.',
$min->formatForDisplay()));
}
}
if ($maximum !== null) {
$max = PhortuneCurrency::newFromString($maximum);
if ($max->value < $this->value) {
throw new Exception(
pht(
'Maximum allowed amount is %s.',
$max->formatForDisplay()));
}
}
return $this;
}
}
diff --git a/src/applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php b/src/applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php
index 8e81d79f5b..bb817263d1 100644
--- a/src/applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php
+++ b/src/applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php
@@ -1,145 +1,162 @@
<?php
final class PhortuneCurrencyTestCase extends PhabricatorTestCase {
public function testCurrencyFormatForDisplay() {
$map = array(
'0' => '$0.00 USD',
'.01' => '$0.01 USD',
'1.00' => '$1.00 USD',
'-1.23' => '$-1.23 USD',
'50000.00' => '$50000.00 USD',
);
foreach ($map as $input => $expect) {
$this->assertEqual(
$expect,
PhortuneCurrency::newFromString($input, 'USD')->formatForDisplay(),
"newFromString({$input})->formatForDisplay()");
}
}
-
public function testCurrencyFormatBareValue() {
// NOTE: The PayPal API depends on the behavior of the bare value format!
$map = array(
'0' => '0.00',
'.01' => '0.01',
'1.00' => '1.00',
'-1.23' => '-1.23',
'50000.00' => '50000.00',
);
foreach ($map as $input => $expect) {
$this->assertEqual(
$expect,
PhortuneCurrency::newFromString($input, 'USD')->formatBareValue(),
"newFromString({$input})->formatBareValue()");
}
}
public function testCurrencyFromString() {
$map = array(
'1.00' => 100,
'1.00 USD' => 100,
'$1.00' => 100,
'$1.00 USD' => 100,
'-$1.00 USD' => -100,
'$-1.00 USD' => -100,
'1' => 100,
'.99' => 99,
'$.99' => 99,
'-$.99' => -99,
'$-.99' => -99,
'$.99 USD' => 99,
);
foreach ($map as $input => $expect) {
$this->assertEqual(
$expect,
PhortuneCurrency::newFromString($input, 'USD')->getValue(),
"newFromString({$input})->getValue()");
}
}
public function testInvalidCurrencyFromString() {
$map = array(
'--1',
'$$1',
'1 JPY',
'buck fiddy',
'1.2.3',
'1 dollar',
);
foreach ($map as $input) {
$caught = null;
try {
PhortuneCurrency::newFromString($input, 'USD');
} catch (Exception $ex) {
$caught = $ex;
}
$this->assertTrue($caught instanceof Exception, "{$input}");
}
}
public function testCurrencyRanges() {
$value = PhortuneCurrency::newFromString('3.00 USD');
$value->assertInRange('2.00 USD', '4.00 USD');
$value->assertInRange('2.00 USD', null);
$value->assertInRange(null, '4.00 USD');
$value->assertInRange(null, null);
$caught = null;
try {
$value->assertInRange('4.00 USD', null);
} catch (Exception $ex) {
$caught = $ex;
}
$this->assertTrue($caught instanceof Exception);
$caught = null;
try {
$value->assertInRange(null, '2.00 USD');
} catch (Exception $ex) {
$caught = $ex;
}
$this->assertTrue($caught instanceof Exception);
$caught = null;
try {
// Minimum and maximum are reversed here.
$value->assertInRange('4.00 USD', '2.00 USD');
} catch (Exception $ex) {
$caught = $ex;
}
$this->assertTrue($caught instanceof Exception);
$credit = PhortuneCurrency::newFromString('-3.00 USD');
$credit->assertInRange('-4.00 USD', '-2.00 USD');
$credit->assertInRange('-4.00 USD', null);
$credit->assertInRange(null, '-2.00 USD');
$credit->assertInRange(null, null);
$caught = null;
try {
$credit->assertInRange('-2.00 USD', null);
} catch (Exception $ex) {
$caught = $ex;
}
$this->assertTrue($caught instanceof Exception);
$caught = null;
try {
$credit->assertInRange(null, '-4.00 USD');
} catch (Exception $ex) {
$caught = $ex;
}
$this->assertTrue($caught instanceof Exception);
}
+ public function testAddCurrency() {
+ $cases = array(
+ array('0.00 USD', '0.00 USD', '$0.00 USD'),
+ array('1.00 USD', '1.00 USD', '$2.00 USD'),
+ array('1.23 USD', '9.77 USD', '$11.00 USD'),
+ );
+
+ foreach ($cases as $case) {
+ list($l, $r, $expect) = $case;
+
+ $l = PhortuneCurrency::newFromString($l);
+ $r = PhortuneCurrency::newFromString($r);
+ $sum = $l->add($r);
+
+ $this->assertEqual($expect, $sum->formatForDisplay());
+ }
+ }
+
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 17:20 (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1126896
Default Alt Text
(27 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment