mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-02 19:01:03 +01:00
Support basic ad-hoc invoices in Phortune
Summary: This allows a merchant to send a user an invoice for something arbitrary, like services rendered. Two major missing parts: - These don't actually get marked as invoices. I'll fix that in the next diff, but it's not entirely trivial because `subscriptionPHID` is currently overloaded to also mean "is invoice". - We don't send email automatically. I don't plan to fix that for now, since all our invoicing needs are covered by personal email. Test Plan: Merchants have a new "new invoice" option: {F376999} This leads to selecting a user and account, and then you can generate the invoice (only one actual "purchase" / line item for the moment). You can add a longer-form remarkup description to contextualize the billable items: {F377001} This sends the invoice and takes you to the merchant order overview screen: {F377002} For now, you copy/paste that link into a nice personal enterprisey business-to-business email; the recipient sees this: {F377003} Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D12478
This commit is contained in:
parent
e0c95bca86
commit
0195e751c6
12 changed files with 436 additions and 18 deletions
|
@ -2817,6 +2817,8 @@ phutil_register_library_map(array(
|
||||||
'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php',
|
'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php',
|
||||||
'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php',
|
'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php',
|
||||||
'PhortuneAccountViewController' => 'applications/phortune/controller/PhortuneAccountViewController.php',
|
'PhortuneAccountViewController' => 'applications/phortune/controller/PhortuneAccountViewController.php',
|
||||||
|
'PhortuneAdHocCart' => 'applications/phortune/cart/PhortuneAdHocCart.php',
|
||||||
|
'PhortuneAdHocProduct' => 'applications/phortune/product/PhortuneAdHocProduct.php',
|
||||||
'PhortuneCart' => 'applications/phortune/storage/PhortuneCart.php',
|
'PhortuneCart' => 'applications/phortune/storage/PhortuneCart.php',
|
||||||
'PhortuneCartAcceptController' => 'applications/phortune/controller/PhortuneCartAcceptController.php',
|
'PhortuneCartAcceptController' => 'applications/phortune/controller/PhortuneCartAcceptController.php',
|
||||||
'PhortuneCartCancelController' => 'applications/phortune/controller/PhortuneCartCancelController.php',
|
'PhortuneCartCancelController' => 'applications/phortune/controller/PhortuneCartCancelController.php',
|
||||||
|
@ -2856,6 +2858,7 @@ phutil_register_library_map(array(
|
||||||
'PhortuneMerchantEditController' => 'applications/phortune/controller/PhortuneMerchantEditController.php',
|
'PhortuneMerchantEditController' => 'applications/phortune/controller/PhortuneMerchantEditController.php',
|
||||||
'PhortuneMerchantEditor' => 'applications/phortune/editor/PhortuneMerchantEditor.php',
|
'PhortuneMerchantEditor' => 'applications/phortune/editor/PhortuneMerchantEditor.php',
|
||||||
'PhortuneMerchantHasMemberEdgeType' => 'applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php',
|
'PhortuneMerchantHasMemberEdgeType' => 'applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php',
|
||||||
|
'PhortuneMerchantInvoiceCreateController' => 'applications/phortune/controller/PhortuneMerchantInvoiceCreateController.php',
|
||||||
'PhortuneMerchantListController' => 'applications/phortune/controller/PhortuneMerchantListController.php',
|
'PhortuneMerchantListController' => 'applications/phortune/controller/PhortuneMerchantListController.php',
|
||||||
'PhortuneMerchantPHIDType' => 'applications/phortune/phid/PhortuneMerchantPHIDType.php',
|
'PhortuneMerchantPHIDType' => 'applications/phortune/phid/PhortuneMerchantPHIDType.php',
|
||||||
'PhortuneMerchantQuery' => 'applications/phortune/query/PhortuneMerchantQuery.php',
|
'PhortuneMerchantQuery' => 'applications/phortune/query/PhortuneMerchantQuery.php',
|
||||||
|
@ -6265,6 +6268,8 @@ phutil_register_library_map(array(
|
||||||
'PhortuneAccountTransaction' => 'PhabricatorApplicationTransaction',
|
'PhortuneAccountTransaction' => 'PhabricatorApplicationTransaction',
|
||||||
'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||||
'PhortuneAccountViewController' => 'PhortuneController',
|
'PhortuneAccountViewController' => 'PhortuneController',
|
||||||
|
'PhortuneAdHocCart' => 'PhortuneCartImplementation',
|
||||||
|
'PhortuneAdHocProduct' => 'PhortuneProductImplementation',
|
||||||
'PhortuneCart' => array(
|
'PhortuneCart' => array(
|
||||||
'PhortuneDAO',
|
'PhortuneDAO',
|
||||||
'PhabricatorApplicationTransactionInterface',
|
'PhabricatorApplicationTransactionInterface',
|
||||||
|
@ -6312,6 +6317,7 @@ phutil_register_library_map(array(
|
||||||
'PhortuneMerchantEditController' => 'PhortuneMerchantController',
|
'PhortuneMerchantEditController' => 'PhortuneMerchantController',
|
||||||
'PhortuneMerchantEditor' => 'PhabricatorApplicationTransactionEditor',
|
'PhortuneMerchantEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
'PhortuneMerchantHasMemberEdgeType' => 'PhabricatorEdgeType',
|
'PhortuneMerchantHasMemberEdgeType' => 'PhabricatorEdgeType',
|
||||||
|
'PhortuneMerchantInvoiceCreateController' => 'PhortuneMerchantController',
|
||||||
'PhortuneMerchantListController' => 'PhortuneMerchantController',
|
'PhortuneMerchantListController' => 'PhortuneMerchantController',
|
||||||
'PhortuneMerchantPHIDType' => 'PhabricatorPHIDType',
|
'PhortuneMerchantPHIDType' => 'PhabricatorPHIDType',
|
||||||
'PhortuneMerchantQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PhortuneMerchantQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
|
|
|
@ -84,19 +84,24 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication {
|
||||||
'edit/(?:(?P<id>\d+)/)?' => 'PhortuneMerchantEditController',
|
'edit/(?:(?P<id>\d+)/)?' => 'PhortuneMerchantEditController',
|
||||||
'orders/(?P<merchantID>\d+)/(?:query/(?P<queryKey>[^/]+)/)?'
|
'orders/(?P<merchantID>\d+)/(?:query/(?P<queryKey>[^/]+)/)?'
|
||||||
=> 'PhortuneCartListController',
|
=> 'PhortuneCartListController',
|
||||||
'(?P<merchantID>\d+)/cart/(?P<id>\d+)/' => array(
|
'(?P<merchantID>\d+)/' => array(
|
||||||
'' => 'PhortuneCartViewController',
|
'cart/(?P<id>\d+)/' => array(
|
||||||
'(?P<action>cancel|refund)/' => 'PhortuneCartCancelController',
|
'' => 'PhortuneCartViewController',
|
||||||
'update/' => 'PhortuneCartUpdateController',
|
'(?P<action>cancel|refund)/' => 'PhortuneCartCancelController',
|
||||||
'accept/' => 'PhortuneCartAcceptController',
|
'update/' => 'PhortuneCartUpdateController',
|
||||||
),
|
'accept/' => 'PhortuneCartAcceptController',
|
||||||
'(?P<merchantID>\d+)/subscription/' => array(
|
),
|
||||||
'(?:query/(?P<queryKey>[^/]+)/)?'
|
'subscription/' => array(
|
||||||
=> 'PhortuneSubscriptionListController',
|
'(?:query/(?P<queryKey>[^/]+)/)?'
|
||||||
'view/(?P<id>\d+)/'
|
=> 'PhortuneSubscriptionListController',
|
||||||
=> 'PhortuneSubscriptionViewController',
|
'view/(?P<id>\d+)/'
|
||||||
'order/(?P<subscriptionID>\d+)/'
|
=> 'PhortuneSubscriptionViewController',
|
||||||
=> 'PhortuneCartListController',
|
'order/(?P<subscriptionID>\d+)/'
|
||||||
|
=> 'PhortuneCartListController',
|
||||||
|
),
|
||||||
|
'invoice/' => array(
|
||||||
|
'new/' => 'PhortuneMerchantInvoiceCreateController',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
'(?P<id>\d+)/' => 'PhortuneMerchantViewController',
|
'(?P<id>\d+)/' => 'PhortuneMerchantViewController',
|
||||||
),
|
),
|
||||||
|
|
39
src/applications/phortune/cart/PhortuneAdHocCart.php
Normal file
39
src/applications/phortune/cart/PhortuneAdHocCart.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhortuneAdHocCart extends PhortuneCartImplementation {
|
||||||
|
|
||||||
|
public function loadImplementationsForCarts(
|
||||||
|
PhabricatorUser $viewer,
|
||||||
|
array $carts) {
|
||||||
|
|
||||||
|
$results = array();
|
||||||
|
foreach ($carts as $key => $cart) {
|
||||||
|
$results[$key] = new PhortuneAdHocCart();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(PhortuneCart $cart) {
|
||||||
|
return $cart->getMetadataValue('adhoc.title');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(PhortuneCart $cart) {
|
||||||
|
return $cart->getMetadataValue('adhoc.description');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCancelURI(PhortuneCart $cart) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDoneURI(PhortuneCart $cart) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function willCreateCart(
|
||||||
|
PhabricatorUser $viewer,
|
||||||
|
PhortuneCart $cart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,6 +16,10 @@ abstract class PhortuneCartImplementation {
|
||||||
abstract public function getCancelURI(PhortuneCart $cart);
|
abstract public function getCancelURI(PhortuneCart $cart);
|
||||||
abstract public function getDoneURI(PhortuneCart $cart);
|
abstract public function getDoneURI(PhortuneCart $cart);
|
||||||
|
|
||||||
|
public function getDescription(PhortuneCart $cart) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public function getDoneActionName(PhortuneCart $cart) {
|
public function getDoneActionName(PhortuneCart $cart) {
|
||||||
return pht('Return to Application');
|
return pht('Return to Application');
|
||||||
}
|
}
|
||||||
|
|
|
@ -209,6 +209,8 @@ final class PhortuneCartCheckoutController
|
||||||
->appendChild($form)
|
->appendChild($form)
|
||||||
->appendChild($provider_form);
|
->appendChild($provider_form);
|
||||||
|
|
||||||
|
$description_box = $this->renderCartDescription($cart);
|
||||||
|
|
||||||
$crumbs = $this->buildApplicationCrumbs();
|
$crumbs = $this->buildApplicationCrumbs();
|
||||||
$crumbs->addTextCrumb(pht('Checkout'));
|
$crumbs->addTextCrumb(pht('Checkout'));
|
||||||
$crumbs->addTextCrumb($title);
|
$crumbs->addTextCrumb($title);
|
||||||
|
@ -217,6 +219,7 @@ final class PhortuneCartCheckoutController
|
||||||
array(
|
array(
|
||||||
$crumbs,
|
$crumbs,
|
||||||
$cart_box,
|
$cart_box,
|
||||||
|
$description_box,
|
||||||
$payment_box,
|
$payment_box,
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
|
|
|
@ -42,4 +42,26 @@ abstract class PhortuneCartController
|
||||||
return $table;
|
return $table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function renderCartDescription(PhortuneCart $cart) {
|
||||||
|
$description = $cart->getDescription();
|
||||||
|
if (!strlen($description)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = PhabricatorMarkupEngine::renderOneObject(
|
||||||
|
id(new PhabricatorMarkupOneOff())
|
||||||
|
->setPreserveLinebreaks(true)
|
||||||
|
->setContent($description),
|
||||||
|
'default',
|
||||||
|
$this->getViewer());
|
||||||
|
|
||||||
|
$box = id(new PHUIBoxView())
|
||||||
|
->addMargin(PHUI::MARGIN_LARGE)
|
||||||
|
->appendChild($output);
|
||||||
|
|
||||||
|
return id(new PHUIObjectBoxView())
|
||||||
|
->setHeaderText(pht('Description'))
|
||||||
|
->appendChild($box);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,16 @@ final class PhortuneCartViewController
|
||||||
|
|
||||||
$authority = $this->loadMerchantAuthority();
|
$authority = $this->loadMerchantAuthority();
|
||||||
|
|
||||||
$cart = id(new PhortuneCartQuery())
|
$query = id(new PhortuneCartQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withIDs(array($this->id))
|
->withIDs(array($this->id))
|
||||||
->needPurchases(true)
|
->needPurchases(true);
|
||||||
->executeOne();
|
|
||||||
|
if ($authority) {
|
||||||
|
$query->withMerchantPHIDs(array($authority->getPHID()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$cart = $query->executeOne();
|
||||||
if (!$cart) {
|
if (!$cart) {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
|
@ -35,6 +40,31 @@ final class PhortuneCartViewController
|
||||||
$error_view = null;
|
$error_view = null;
|
||||||
$resume_uri = null;
|
$resume_uri = null;
|
||||||
switch ($cart->getStatus()) {
|
switch ($cart->getStatus()) {
|
||||||
|
case PhortuneCart::STATUS_READY:
|
||||||
|
if ($authority && $request->getStr('invoice')) {
|
||||||
|
// We arrived here by following the ad-hoc invoice workflow, and
|
||||||
|
// are acting with merchant authority.
|
||||||
|
|
||||||
|
$checkout_uri = PhabricatorEnv::getURI($cart->getCheckoutURI());
|
||||||
|
|
||||||
|
$invoice_message = array(
|
||||||
|
pht(
|
||||||
|
'Manual invoices do not automatically notify recipients yet. '.
|
||||||
|
'Send the payer this checkout link:'),
|
||||||
|
' ',
|
||||||
|
phutil_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'href' => $checkout_uri,
|
||||||
|
),
|
||||||
|
$checkout_uri),
|
||||||
|
);
|
||||||
|
|
||||||
|
$error_view = id(new PHUIInfoView())
|
||||||
|
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
|
||||||
|
->setErrors(array($invoice_message));
|
||||||
|
}
|
||||||
|
break;
|
||||||
case PhortuneCart::STATUS_PURCHASING:
|
case PhortuneCart::STATUS_PURCHASING:
|
||||||
if ($can_edit) {
|
if ($can_edit) {
|
||||||
$resume_uri = $cart->getMetadataValue('provider.checkoutURI');
|
$resume_uri = $cart->getMetadataValue('provider.checkoutURI');
|
||||||
|
@ -86,7 +116,6 @@ final class PhortuneCartViewController
|
||||||
$error_view = id(new PHUIInfoView())
|
$error_view = id(new PHUIInfoView())
|
||||||
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
|
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
|
||||||
->appendChild(pht('This purchase has been completed.'));
|
->appendChild(pht('This purchase has been completed.'));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,6 +155,8 @@ final class PhortuneCartViewController
|
||||||
$cart_box->setInfoView($error_view);
|
$cart_box->setInfoView($error_view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$description = $this->renderCartDescription($cart);
|
||||||
|
|
||||||
$charges = id(new PhortuneChargeQuery())
|
$charges = id(new PhortuneChargeQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withCartPHIDs(array($cart->getPHID()))
|
->withCartPHIDs(array($cart->getPHID()))
|
||||||
|
@ -171,6 +202,7 @@ final class PhortuneCartViewController
|
||||||
array(
|
array(
|
||||||
$crumbs,
|
$crumbs,
|
||||||
$cart_box,
|
$cart_box,
|
||||||
|
$description,
|
||||||
$charges,
|
$charges,
|
||||||
$timeline,
|
$timeline,
|
||||||
),
|
),
|
||||||
|
|
|
@ -0,0 +1,245 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhortuneMerchantInvoiceCreateController
|
||||||
|
extends PhortuneMerchantController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$viewer = $request->getUser();
|
||||||
|
|
||||||
|
$merchant = $this->loadMerchantAuthority();
|
||||||
|
if (!$merchant) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$merchant_id = $merchant->getID();
|
||||||
|
$cancel_uri = $this->getApplicationURI("/merchant/{$merchant_id}/");
|
||||||
|
|
||||||
|
// Load the user to invoice, or prompt the viewer to select one.
|
||||||
|
$target_user = null;
|
||||||
|
$user_phid = head($request->getArr('userPHID'));
|
||||||
|
if (!$user_phid) {
|
||||||
|
$user_phid = $request->getStr('userPHID');
|
||||||
|
}
|
||||||
|
if ($user_phid) {
|
||||||
|
$target_user = id(new PhabricatorPeopleQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs(array($user_phid))
|
||||||
|
->executeOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$target_user) {
|
||||||
|
$form = id(new AphrontFormView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->appendRemarkupInstructions(pht('Choose a user to invoice.'))
|
||||||
|
->appendControl(
|
||||||
|
id(new AphrontFormTokenizerControl())
|
||||||
|
->setLabel(pht('User'))
|
||||||
|
->setDatasource(new PhabricatorPeopleDatasource())
|
||||||
|
->setName('userPHID')
|
||||||
|
->setLimit(1));
|
||||||
|
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle(pht('Choose User'))
|
||||||
|
->appendForm($form)
|
||||||
|
->addCancelButton($cancel_uri)
|
||||||
|
->addSubmitButton(pht('Continue'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the account to invoice, or prompt the viewer to select one.
|
||||||
|
$target_account = null;
|
||||||
|
$account_phid = $request->getStr('accountPHID');
|
||||||
|
if ($account_phid) {
|
||||||
|
$target_account = id(new PhortuneAccountQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs(array($account_phid))
|
||||||
|
->withMemberPHIDs(array($target_user->getPHID()))
|
||||||
|
->executeOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$target_account) {
|
||||||
|
$accounts = PhortuneAccountQuery::loadAccountsForUser(
|
||||||
|
$target_user,
|
||||||
|
PhabricatorContentSource::newFromRequest($request));
|
||||||
|
|
||||||
|
$form = id(new AphrontFormView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->addHiddenInput('userPHID', $target_user->getPHID())
|
||||||
|
->appendRemarkupInstructions(pht('Choose which account to invoice.'))
|
||||||
|
->appendControl(
|
||||||
|
id(new AphrontFormMarkupControl())
|
||||||
|
->setLabel(pht('User'))
|
||||||
|
->setValue($viewer->renderHandle($target_user->getPHID())))
|
||||||
|
->appendControl(
|
||||||
|
id(new AphrontFormSelectControl())
|
||||||
|
->setLabel(pht('Account'))
|
||||||
|
->setName('accountPHID')
|
||||||
|
->setValue($account_phid)
|
||||||
|
->setOptions(mpull($accounts, 'getName', 'getPHID')));
|
||||||
|
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle(pht('Choose Account'))
|
||||||
|
->appendForm($form)
|
||||||
|
->addCancelButton($cancel_uri)
|
||||||
|
->addSubmitButton(pht('Continue'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Now we build the actual invoice.
|
||||||
|
$title = pht('New Invoice');
|
||||||
|
|
||||||
|
$crumbs = $this->buildApplicationCrumbs();
|
||||||
|
$crumbs->addTextCrumb($merchant->getName());
|
||||||
|
|
||||||
|
$v_title = $request->getStr('title');
|
||||||
|
$e_title = true;
|
||||||
|
|
||||||
|
$v_name = $request->getStr('name');
|
||||||
|
$e_name = true;
|
||||||
|
|
||||||
|
$v_cost = $request->getStr('cost');
|
||||||
|
$e_cost = true;
|
||||||
|
|
||||||
|
$v_desc = $request->getStr('description');
|
||||||
|
|
||||||
|
$v_quantity = 1;
|
||||||
|
$e_quantity = null;
|
||||||
|
|
||||||
|
$errors = array();
|
||||||
|
if ($request->isFormPost() && $request->getStr('invoice')) {
|
||||||
|
$v_quantity = $request->getStr('quantity');
|
||||||
|
|
||||||
|
$e_title = null;
|
||||||
|
$e_name = null;
|
||||||
|
$e_cost = null;
|
||||||
|
$e_quantity = null;
|
||||||
|
|
||||||
|
if (!strlen($v_title)) {
|
||||||
|
$e_title = pht('Required');
|
||||||
|
$errors[] = pht('You must title this invoice.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strlen($v_name)) {
|
||||||
|
$e_name = pht('Required');
|
||||||
|
$errors[] = pht('You must provide a name for this purchase.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strlen($v_cost)) {
|
||||||
|
$e_cost = pht('Required');
|
||||||
|
$errors[] = pht('You must provide a cost for this purchase.');
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$v_currency = PhortuneCurrency::newFromUserInput(
|
||||||
|
$viewer,
|
||||||
|
$v_cost);
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
$errors[] = $ex->getMessage();
|
||||||
|
$e_cost = pht('Invalid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((int)$v_quantity <= 0) {
|
||||||
|
$e_quantity = pht('Invalid');
|
||||||
|
$errors[] = pht('Quantity must be a positive integer.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$errors) {
|
||||||
|
$unique = Filesystem::readRandomCharacters(16);
|
||||||
|
|
||||||
|
$product = id(new PhortuneProductQuery())
|
||||||
|
->setViewer($target_user)
|
||||||
|
->withClassAndRef('PhortuneAdHocProduct', $unique)
|
||||||
|
->executeOne();
|
||||||
|
|
||||||
|
$cart_implementation = new PhortuneAdHocCart();
|
||||||
|
|
||||||
|
$cart = $target_account->newCart(
|
||||||
|
$target_user,
|
||||||
|
$cart_implementation,
|
||||||
|
$merchant);
|
||||||
|
|
||||||
|
$cart
|
||||||
|
->setMetadataValue('adhoc.title', $v_title)
|
||||||
|
->setMetadataValue('adhoc.description', $v_desc);
|
||||||
|
|
||||||
|
$purchase = $cart->newPurchase($target_user, $product)
|
||||||
|
->setBasePriceAsCurrency($v_currency)
|
||||||
|
->setQuantity((int)$v_quantity)
|
||||||
|
->setMetadataValue('adhoc.name', $v_name)
|
||||||
|
->save();
|
||||||
|
|
||||||
|
// TODO: Actually mark these as invoices. Right now, there's no easy
|
||||||
|
// way to do that.
|
||||||
|
|
||||||
|
$cart->activateCart();
|
||||||
|
$cart_id = $cart->getID();
|
||||||
|
|
||||||
|
$uri = "/merchant/{$merchant_id}/cart/{$cart_id}/?invoice=true";
|
||||||
|
$uri = $this->getApplicationURI($uri);
|
||||||
|
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$form = id(new AphrontFormView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->addHiddenInput('userPHID', $target_user->getPHID())
|
||||||
|
->addHiddenInput('accountPHID', $target_account->getPHID())
|
||||||
|
->addHiddenInput('invoice', true)
|
||||||
|
->appendControl(
|
||||||
|
id(new AphrontFormMarkupControl())
|
||||||
|
->setLabel(pht('User'))
|
||||||
|
->setValue($viewer->renderHandle($target_user->getPHID())))
|
||||||
|
->appendControl(
|
||||||
|
id(new AphrontFormMarkupControl())
|
||||||
|
->setLabel(pht('Account'))
|
||||||
|
->setValue($viewer->renderHandle($target_account->getPHID())))
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormTextControl())
|
||||||
|
->setLabel(pht('Invoice Title'))
|
||||||
|
->setName('title')
|
||||||
|
->setValue($v_title)
|
||||||
|
->setError($e_title))
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormTextControl())
|
||||||
|
->setLabel(pht('Purchase Name'))
|
||||||
|
->setName('name')
|
||||||
|
->setValue($v_name)
|
||||||
|
->setError($e_name))
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormTextControl())
|
||||||
|
->setLabel(pht('Purchase Cost'))
|
||||||
|
->setName('cost')
|
||||||
|
->setValue($v_cost)
|
||||||
|
->setError($e_cost))
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormTextControl())
|
||||||
|
->setLabel(pht('Quantity'))
|
||||||
|
->setName('quantity')
|
||||||
|
->setValue($v_quantity)
|
||||||
|
->setError($e_quantity))
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormTextAreaControl())
|
||||||
|
->setLabel(pht('Invoice Description'))
|
||||||
|
->setName('description')
|
||||||
|
->setValue($v_desc))
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormSubmitControl())
|
||||||
|
->addCancelButton($cancel_uri)
|
||||||
|
->setValue(pht('Send Invoice')));
|
||||||
|
|
||||||
|
$box = id(new PHUIObjectBoxView())
|
||||||
|
->setHeaderText(pht('New Invoice'))
|
||||||
|
->setFormErrors($errors)
|
||||||
|
->appendChild($form);
|
||||||
|
|
||||||
|
return $this->buildApplicationPage(
|
||||||
|
array(
|
||||||
|
$crumbs,
|
||||||
|
$box,
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => $title,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -192,6 +192,15 @@ final class PhortuneMerchantViewController
|
||||||
->setDisabled(!$can_edit)
|
->setDisabled(!$can_edit)
|
||||||
->setWorkflow(!$can_edit));
|
->setWorkflow(!$can_edit));
|
||||||
|
|
||||||
|
|
||||||
|
$view->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setName(pht('New Invoice'))
|
||||||
|
->setIcon('fa-fax')
|
||||||
|
->setHref($this->getApplicationURI("merchant/{$id}/invoice/new/"))
|
||||||
|
->setDisabled(!$can_edit)
|
||||||
|
->setWorkflow(!$can_edit));
|
||||||
|
|
||||||
return $view;
|
return $view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
42
src/applications/phortune/product/PhortuneAdHocProduct.php
Normal file
42
src/applications/phortune/product/PhortuneAdHocProduct.php
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhortuneAdHocProduct extends PhortuneProductImplementation {
|
||||||
|
|
||||||
|
private $ref;
|
||||||
|
|
||||||
|
public function loadImplementationsForRefs(
|
||||||
|
PhabricatorUser $viewer,
|
||||||
|
array $refs) {
|
||||||
|
|
||||||
|
$results = array();
|
||||||
|
foreach ($refs as $key => $ref) {
|
||||||
|
$product = new PhortuneAdHocProduct();
|
||||||
|
$product->ref = $ref;
|
||||||
|
$results[$key] = $product;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRef() {
|
||||||
|
return $this->ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(PhortuneProduct $product) {
|
||||||
|
return pht('Ad-Hoc Product');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPurchaseName(
|
||||||
|
PhortuneProduct $product,
|
||||||
|
PhortunePurchase $purchase) {
|
||||||
|
|
||||||
|
return coalesce(
|
||||||
|
$purchase->getMetadataValue('adhoc.name'),
|
||||||
|
$this->getName($product));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPriceAsCurrency(PhortuneProduct $product) {
|
||||||
|
return PhortuneCurrency::newEmptyCurrency();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -453,6 +453,10 @@ final class PhortuneCart extends PhortuneDAO
|
||||||
return $this->getImplementation()->getCancelURI($this);
|
return $this->getImplementation()->getCancelURI($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDescription() {
|
||||||
|
return $this->getImplementation()->getDescription($this);
|
||||||
|
}
|
||||||
|
|
||||||
public function getDetailURI(PhortuneMerchant $authority = null) {
|
public function getDetailURI(PhortuneMerchant $authority = null) {
|
||||||
if ($authority) {
|
if ($authority) {
|
||||||
$prefix = 'merchant/'.$authority->getID().'/';
|
$prefix = 'merchant/'.$authority->getID().'/';
|
||||||
|
|
|
@ -92,7 +92,14 @@ final class PhortunePurchase extends PhortuneDAO
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTotalPriceAsCurrency() {
|
public function getTotalPriceAsCurrency() {
|
||||||
return $this->getBasePriceAsCurrency();
|
$base = $this->getBasePriceAsCurrency();
|
||||||
|
|
||||||
|
$price = PhortuneCurrency::newEmptyCurrency();
|
||||||
|
for ($ii = 0; $ii < $this->getQuantity(); $ii++) {
|
||||||
|
$price = $price->add($base);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $price;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMetadataValue($key, $default = null) {
|
public function getMetadataValue($key, $default = null) {
|
||||||
|
|
Loading…
Reference in a new issue