Page MenuHomePhorge

No OneTemporary

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

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)

Event Timeline