diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 8c4ba2ef05..8725524a29 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -89,6 +89,7 @@ return array( 'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49', 'rsrc/css/application/pholio/pholio.css' => 'ca89d380', 'rsrc/css/application/phortune/phortune-credit-card-form.css' => '8391eb02', + 'rsrc/css/application/phortune/phortune-invoice.css' => '476055e2', 'rsrc/css/application/phortune/phortune.css' => '5b99dae0', 'rsrc/css/application/phrequent/phrequent.css' => 'ffc185ad', 'rsrc/css/application/phriction/phriction-document-css.css' => '4282e4ad', @@ -840,6 +841,7 @@ return array( 'phortune-credit-card-form' => '2290aeef', 'phortune-credit-card-form-css' => '8391eb02', 'phortune-css' => '5b99dae0', + 'phortune-invoice-css' => '476055e2', 'phrequent-css' => 'ffc185ad', 'phriction-document-css' => '4282e4ad', 'phui-action-panel-css' => '91c7b835', diff --git a/resources/sql/autopatches/20161029.phortune.invoice.1.sql b/resources/sql/autopatches/20161029.phortune.invoice.1.sql new file mode 100644 index 0000000000..7a4a8a8272 --- /dev/null +++ b/resources/sql/autopatches/20161029.phortune.invoice.1.sql @@ -0,0 +1,5 @@ +ALTER TABLE {$NAMESPACE}_phortune.phortune_merchant + ADD invoiceEmail VARCHAR(255) COLLATE {$COLLATE_TEXT} NOT NULL; + +ALTER TABLE {$NAMESPACE}_phortune.phortune_merchant + ADD invoiceFooter LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b8eb474a20..d91e588a16 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4174,6 +4174,7 @@ phutil_register_library_map(array( 'PhortuneCurrencyTestCase' => 'applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php', 'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php', 'PhortuneErrCode' => 'applications/phortune/constants/PhortuneErrCode.php', + 'PhortuneInvoiceView' => 'applications/phortune/view/PhortuneInvoiceView.php', 'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php', 'PhortuneMemberHasAccountEdgeType' => 'applications/phortune/edge/PhortuneMemberHasAccountEdgeType.php', 'PhortuneMemberHasMerchantEdgeType' => 'applications/phortune/edge/PhortuneMemberHasMerchantEdgeType.php', @@ -9422,6 +9423,7 @@ phutil_register_library_map(array( 'PhortuneCurrencyTestCase' => 'PhabricatorTestCase', 'PhortuneDAO' => 'PhabricatorLiskDAO', 'PhortuneErrCode' => 'PhortuneConstants', + 'PhortuneInvoiceView' => 'AphrontTagView', 'PhortuneLandingController' => 'PhortuneController', 'PhortuneMemberHasAccountEdgeType' => 'PhabricatorEdgeType', 'PhortuneMemberHasMerchantEdgeType' => 'PhabricatorEdgeType', diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index 434cc4119a..c87914b79f 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -61,6 +61,7 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { 'cart/(?P\d+)/' => array( '' => 'PhortuneCartViewController', 'checkout/' => 'PhortuneCartCheckoutController', + '(?Pprint)/' => 'PhortuneCartViewController', '(?Pcancel|refund)/' => 'PhortuneCartCancelController', 'update/' => 'PhortuneCartUpdateController', ), diff --git a/src/applications/phortune/controller/PhortuneCartViewController.php b/src/applications/phortune/controller/PhortuneCartViewController.php index e105973e11..8387cacb07 100644 --- a/src/applications/phortune/controller/PhortuneCartViewController.php +++ b/src/applications/phortune/controller/PhortuneCartViewController.php @@ -3,9 +3,12 @@ final class PhortuneCartViewController extends PhortuneCartController { + private $action = null; + public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); + $this->action = $request->getURIData('action'); $authority = $this->loadMerchantAuthority(); require_celerity_resource('phortune-css'); @@ -129,7 +132,7 @@ final class PhortuneCartViewController $header = id(new PHUIHeaderView()) ->setUser($viewer) - ->setHeader(pht('Order Detail')) + ->setHeader($cart->getName()) ->setHeaderIcon('fa-shopping-cart'); if ($cart->getStatus() == PhortuneCart::STATUS_PURCHASED) { @@ -188,36 +191,75 @@ final class PhortuneCartViewController $crumbs->addTextCrumb(pht('Cart %d', $cart->getID())); $crumbs->setBorder(true); - $timeline = $this->buildTransactionTimeline( - $cart, - new PhortuneCartTransactionQuery()); - $timeline - ->setShouldTerminate(true); + if (!$this->action) { + $class = 'phortune-cart-page'; + $timeline = $this->buildTransactionTimeline( + $cart, + new PhortuneCartTransactionQuery()); + $timeline + ->setShouldTerminate(true); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setCurtain($curtain) - ->setMainColumn(array( - $error_view, - $details, - $cart_box, - $description, - $charges, - $timeline, - )); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $error_view, + $details, + $cart_box, + $description, + $charges, + $timeline, + )); - return $this->newPage() + } else { + $class = 'phortune-invoice-view'; + $crumbs = null; + $merchant_phid = $cart->getMerchantPHID(); + $buyer_phid = $cart->getAuthorPHID(); + $merchant = id(new PhortuneMerchantQuery()) + ->setViewer($viewer) + ->withPHIDs(array($merchant_phid)) + ->needProfileImage(true) + ->executeOne(); + $buyer = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($buyer_phid)) + ->needProfileImage(true) + ->executeOne(); + // TODO: Add account "Contact" info + + $merchant_contact = new PHUIRemarkupView( + $viewer, $merchant->getContactInfo()); + $description = null; + + $view = id(new PhortuneInvoiceView()) + ->setMerchantName($merchant->getName()) + ->setMerchantLogo($merchant->getProfileImageURI()) + ->setMerchantContact($merchant_contact) + ->setMerchantFooter($merchant->getInvoiceFooter()) + ->setAccountName($buyer->getRealName()) + ->setStatus($error_view) + ->setContent(array( + $description, + $details, + $cart_box, + $charges, + )); + } + + $page = $this->newPage() ->setTitle(pht('Cart %d', $cart->getID())) - ->setCrumbs($crumbs) - ->addClass('phortune-cart-page') + ->addClass($class) ->appendChild($view); + if ($crumbs) { + $page->setCrumbs($crumbs); + } + return $page; } private function buildDetailsView(PhortuneCart $cart) { - $viewer = $this->getViewer(); - $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($cart); @@ -229,9 +271,10 @@ final class PhortuneCartViewController $cart->getMerchantPHID(), )); - $view->addProperty( - pht('Order Name'), - $cart->getName()); + if ($this->action == 'print') { + $view->addProperty(pht('Order Name'), $cart->getName()); + } + $view->addProperty( pht('Account'), $handles[$cart->getAccountPHID()]->renderLink()); @@ -276,7 +319,7 @@ final class PhortuneCartViewController $refund_uri = $this->getApplicationURI("{$prefix}cart/{$id}/refund/"); $update_uri = $this->getApplicationURI("{$prefix}cart/{$id}/update/"); $accept_uri = $this->getApplicationURI("{$prefix}cart/{$id}/accept/"); - $print_uri = $this->getApplicationURI("{$prefix}cart/{$id}/?__print__=1"); + $print_uri = $this->getApplicationURI("{$prefix}cart/{$id}/print/"); $curtain->addAction( id(new PhabricatorActionView()) diff --git a/src/applications/phortune/controller/PhortuneMerchantViewController.php b/src/applications/phortune/controller/PhortuneMerchantViewController.php index 6c74ce8a06..2f5232158d 100644 --- a/src/applications/phortune/controller/PhortuneMerchantViewController.php +++ b/src/applications/phortune/controller/PhortuneMerchantViewController.php @@ -129,6 +129,13 @@ final class PhortuneMerchantViewController $view->addProperty(pht('Status'), $status_view); + $invoice_from = $merchant->getInvoiceEmail(); + if (!$invoice_from) { + $invoice_from = pht('No email address set'); + $invoice_from = phutil_tag('em', array(), $invoice_from); + } + $view->addProperty(pht('Invoice From'), $invoice_from); + $description = $merchant->getDescription(); if (strlen($description)) { $description = new PHUIRemarkupView($viewer, $description); @@ -147,6 +154,15 @@ final class PhortuneMerchantViewController $view->addTextContent($contact_info); } + $footer_info = $merchant->getInvoiceFooter(); + if (strlen($footer_info)) { + $footer_info = new PHUIRemarkupView($viewer, $footer_info); + $view->addSectionHeader( + pht('Invoice Footer'), + PHUIPropertyListView::ICON_SUMMARY); + $view->addTextContent($footer_info); + } + return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Details')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) diff --git a/src/applications/phortune/editor/PhortuneMerchantEditEngine.php b/src/applications/phortune/editor/PhortuneMerchantEditEngine.php index 8c2356e6fe..bcc188f7e3 100644 --- a/src/applications/phortune/editor/PhortuneMerchantEditEngine.php +++ b/src/applications/phortune/editor/PhortuneMerchantEditEngine.php @@ -84,6 +84,21 @@ final class PhortuneMerchantEditEngine ->setTransactionType(PhortuneMerchantTransaction::TYPE_NAME) ->setValue($object->getName()), + id(new PhabricatorUsersEditField()) + ->setKey('members') + ->setAliases(array('memberPHIDs')) + ->setLabel(pht('Members')) + ->setUseEdgeTransactions(true) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue( + 'edge:type', + PhortuneMerchantHasMemberEdgeType::EDGECONST) + ->setDescription(pht('Initial merchant members.')) + ->setConduitDescription(pht('Set merchant members.')) + ->setConduitTypeDescription(pht('New list of members.')) + ->setInitialValue($object->getMemberPHIDs()) + ->setValue($member_phids), + id(new PhabricatorRemarkupEditField()) ->setKey('description') ->setLabel(pht('Description')) @@ -100,20 +115,22 @@ final class PhortuneMerchantEditEngine ->setTransactionType(PhortuneMerchantTransaction::TYPE_CONTACTINFO) ->setValue($object->getContactInfo()), - id(new PhabricatorUsersEditField()) - ->setKey('members') - ->setAliases(array('memberPHIDs')) - ->setLabel(pht('Members')) - ->setUseEdgeTransactions(true) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue( - 'edge:type', - PhortuneMerchantHasMemberEdgeType::EDGECONST) - ->setDescription(pht('Initial merchant members.')) - ->setConduitDescription(pht('Set merchant members.')) - ->setConduitTypeDescription(pht('New list of members.')) - ->setInitialValue($object->getMemberPHIDs()) - ->setValue($member_phids), + id(new PhabricatorTextEditField()) + ->setKey('invoiceEmail') + ->setLabel(pht('Invoice From Email')) + ->setDescription(pht('Email address invoices are sent from.')) + ->setConduitTypeDescription( + pht('Email address invoices are sent from.')) + ->setTransactionType(PhortuneMerchantTransaction::TYPE_INVOICEEMAIL) + ->setValue($object->getInvoiceEmail()), + + id(new PhabricatorRemarkupEditField()) + ->setKey('invoiceFooter') + ->setLabel(pht('Invoice Footer')) + ->setDescription(pht('Footer on invoice forms.')) + ->setConduitTypeDescription(pht('Footer on invoice forms.')) + ->setTransactionType(PhortuneMerchantTransaction::TYPE_INVOICEFOOTER) + ->setValue($object->getInvoiceFooter()), ); } diff --git a/src/applications/phortune/editor/PhortuneMerchantEditor.php b/src/applications/phortune/editor/PhortuneMerchantEditor.php index 2d35ddabfd..e2db3688e7 100644 --- a/src/applications/phortune/editor/PhortuneMerchantEditor.php +++ b/src/applications/phortune/editor/PhortuneMerchantEditor.php @@ -18,6 +18,8 @@ final class PhortuneMerchantEditor $types[] = PhortuneMerchantTransaction::TYPE_DESCRIPTION; $types[] = PhortuneMerchantTransaction::TYPE_CONTACTINFO; $types[] = PhortuneMerchantTransaction::TYPE_PICTURE; + $types[] = PhortuneMerchantTransaction::TYPE_INVOICEEMAIL; + $types[] = PhortuneMerchantTransaction::TYPE_INVOICEFOOTER; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDGE; @@ -34,6 +36,10 @@ final class PhortuneMerchantEditor return $object->getDescription(); case PhortuneMerchantTransaction::TYPE_CONTACTINFO: return $object->getContactInfo(); + case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: + return $object->getInvoiceEmail(); + case PhortuneMerchantTransaction::TYPE_INVOICEFOOTER: + return $object->getInvoiceFooter(); case PhortuneMerchantTransaction::TYPE_PICTURE: return $object->getProfileImagePHID(); } @@ -49,6 +55,8 @@ final class PhortuneMerchantEditor case PhortuneMerchantTransaction::TYPE_NAME: case PhortuneMerchantTransaction::TYPE_DESCRIPTION: case PhortuneMerchantTransaction::TYPE_CONTACTINFO: + case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: + case PhortuneMerchantTransaction::TYPE_INVOICEFOOTER: case PhortuneMerchantTransaction::TYPE_PICTURE: return $xaction->getNewValue(); } @@ -70,6 +78,12 @@ final class PhortuneMerchantEditor case PhortuneMerchantTransaction::TYPE_CONTACTINFO: $object->setContactInfo($xaction->getNewValue()); return; + case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: + $object->setInvoiceEmail($xaction->getNewValue()); + return; + case PhortuneMerchantTransaction::TYPE_INVOICEFOOTER: + $object->setInvoiceFooter($xaction->getNewValue()); + return; case PhortuneMerchantTransaction::TYPE_PICTURE: $object->setProfileImagePHID($xaction->getNewValue()); return; @@ -86,6 +100,8 @@ final class PhortuneMerchantEditor case PhortuneMerchantTransaction::TYPE_NAME: case PhortuneMerchantTransaction::TYPE_DESCRIPTION: case PhortuneMerchantTransaction::TYPE_CONTACTINFO: + case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: + case PhortuneMerchantTransaction::TYPE_INVOICEFOOTER: case PhortuneMerchantTransaction::TYPE_PICTURE: return; } @@ -116,6 +132,29 @@ final class PhortuneMerchantEditor $error->setIsMissingFieldError(true); $errors[] = $error; } + break; + case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PhortuneMerchantTransaction::TYPE_INVOICEEMAIL: + $new_email = $xaction->getNewValue(); + break; + } + } + if (strlen($new_email)) { + $email = new PhutilEmailAddress($new_email); + $domain = $email->getDomainName(); + + if (!$domain) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('%s is not a valid email.', $new_email), + nonempty(last($xactions), null)); + + $errors[] = $error; + } + } break; } diff --git a/src/applications/phortune/storage/PhortuneMerchant.php b/src/applications/phortune/storage/PhortuneMerchant.php index b2117a2662..a57e5bd69e 100644 --- a/src/applications/phortune/storage/PhortuneMerchant.php +++ b/src/applications/phortune/storage/PhortuneMerchant.php @@ -9,6 +9,8 @@ final class PhortuneMerchant extends PhortuneDAO protected $viewPolicy; protected $description; protected $contactInfo; + protected $invoiceEmail; + protected $invoiceFooter; protected $profileImagePHID; private $memberPHIDs = self::ATTACHABLE; @@ -27,6 +29,8 @@ final class PhortuneMerchant extends PhortuneDAO 'name' => 'text255', 'description' => 'text', 'contactInfo' => 'text', + 'invoiceEmail' => 'text255', + 'invoiceFooter' => 'text', 'profileImagePHID' => 'phid?', ), ) + parent::getConfiguration(); diff --git a/src/applications/phortune/storage/PhortuneMerchantTransaction.php b/src/applications/phortune/storage/PhortuneMerchantTransaction.php index 48895d85ec..c5f8db6d43 100644 --- a/src/applications/phortune/storage/PhortuneMerchantTransaction.php +++ b/src/applications/phortune/storage/PhortuneMerchantTransaction.php @@ -6,6 +6,8 @@ final class PhortuneMerchantTransaction const TYPE_NAME = 'merchant:name'; const TYPE_DESCRIPTION = 'merchant:description'; const TYPE_CONTACTINFO = 'merchant:contactinfo'; + const TYPE_INVOICEEMAIL = 'merchant:invoiceemail'; + const TYPE_INVOICEFOOTER = 'merchant:invoicefooter'; const TYPE_PICTURE = 'merchant:picture'; public function getApplicationName() { @@ -48,6 +50,14 @@ final class PhortuneMerchantTransaction return pht( '%s updated the contact information for this merchant.', $this->renderHandleLink($author_phid)); + case self::TYPE_INVOICEEMAIL: + return pht( + '%s updated the invoice email for this merchant.', + $this->renderHandleLink($author_phid)); + case self::TYPE_INVOICEFOOTER: + return pht( + '%s updated the invoice footer for this merchant.', + $this->renderHandleLink($author_phid)); } return parent::getTitle(); @@ -58,6 +68,8 @@ final class PhortuneMerchantTransaction switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: case self::TYPE_CONTACTINFO: + case self::TYPE_INVOICEEMAIL: + case self::TYPE_INVOICEFOOTER: return ($old === null); } return parent::shouldHide(); @@ -69,6 +81,10 @@ final class PhortuneMerchantTransaction return ($this->getOldValue() !== null); case self::TYPE_CONTACTINFO: return ($this->getOldValue() !== null); + case self::TYPE_INVOICEEMAIL: + return ($this->getOldValue() !== null); + case self::TYPE_INVOICEFOOTER: + return ($this->getOldValue() !== null); } return parent::hasChangeDetails(); diff --git a/src/applications/phortune/view/PhortuneInvoiceView.php b/src/applications/phortune/view/PhortuneInvoiceView.php new file mode 100644 index 0000000000..89ad3fd1bc --- /dev/null +++ b/src/applications/phortune/view/PhortuneInvoiceView.php @@ -0,0 +1,159 @@ +merchantName = $name; + return $this; + } + + public function setMerchantLogo($logo) { + $this->merchantLogo = $logo; + return $this; + } + + public function setMerchantContact($contact) { + $this->merchantContact = $contact; + return $this; + } + + public function setMerchantFooter($footer) { + $this->merchantFooter = $footer; + return $this; + } + + public function setAccountName($name) { + $this->accountName = $name; + return $this; + } + + public function setAccountContact($contact) { + $this->accountContact = $contact; + return $this; + } + + public function setStatus($status) { + $this->status = $status; + return $this; + } + + public function setContent($content) { + $this->content = $content; + return $this; + } + + protected function getTagAttributes() { + $classes = array(); + $classes[] = 'phortune-invoice-view'; + + return array( + 'class' => implode(' ', $classes), + ); + } + + protected function getTagContent() { + require_celerity_resource('phortune-invoice-css'); + + $logo = phutil_tag( + 'div', + array( + 'class' => 'phortune-invoice-logo', + ), + phutil_tag( + 'img', + array( + 'height' => '50', + 'width' => '50', + 'alt' => $this->merchantName, + 'src' => $this->merchantLogo, + ))); + + $to_title = phutil_tag( + 'div', + array( + 'class' => 'phortune-mini-header', + ), + pht('To:')); + + $bill_to = phutil_tag( + 'td', + array( + 'class' => 'phortune-invoice-to', + 'width' => '50%', + ), + array( + $to_title, + phutil_tag('strong', array(), $this->accountName), + phutil_tag('br', array()), + $this->accountContact, + )); + + $from_title = phutil_tag( + 'div', + array( + 'class' => 'phortune-mini-header', + ), + pht('From:')); + + $bill_from = phutil_tag( + 'td', + array( + 'class' => 'phortune-invoice-from', + 'width' => '50%', + ), + array( + $from_title, + phutil_tag('strong', array(), $this->merchantName), + phutil_tag('br', array()), + $this->merchantContact, + )); + + $contact = phutil_tag( + 'table', + array( + 'class' => 'phortune-invoice-contact', + 'width' => '100%', + ), + phutil_tag( + 'tr', + array(), + array( + $bill_to, + $bill_from, + ))); + + $status = null; + if ($this->status) { + $status = phutil_tag( + 'div', + array( + 'class' => 'phortune-invoice-status', + ), + $this->status); + } + + $footer = phutil_tag( + 'div', + array( + 'class' => 'phortune-invoice-footer', + ), + $this->merchantFooter); + + return array( + $logo, + $contact, + $status, + $this->content, + $footer, + ); + } +} diff --git a/webroot/rsrc/css/application/phortune/phortune-invoice.css b/webroot/rsrc/css/application/phortune/phortune-invoice.css new file mode 100644 index 0000000000..34bceb4bba --- /dev/null +++ b/webroot/rsrc/css/application/phortune/phortune-invoice.css @@ -0,0 +1,75 @@ +/** + * @provides phortune-invoice-css + */ + +.phortune-invoice-view { + max-width: 800px; + margin: 16px auto; + background: #fff; +} + +.phortune-invoice-view .phabricator-main-menu { + display: none; +} + +.phortune-invoice-view .phabricator-standard-page-footer { + display: none; +} + +.device-desktop .phortune-invoice-view .phui-property-list-key { + width: 16%; +} + +.device-desktop .phortune-invoice-view .phui-property-list-value { + width: 80%; +} + +.phortune-invoice-logo { + margin-bottom: 24px; +} + +.phortune-invoice-logo img { + margin: 0 auto; +} + +.phortune-invoice-contact { + margin-bottom: 32px; +} + +.phortune-invoice-contact td { + padding: 4px 16px; +} + +.phortune-invoice-to { + border-right: 1px solid {$lightblueborder}; +} + +.phortune-mini-header { + color: {$lightbluetext}; + font-weight: bold; + text-transform: uppercase; + margin-bottom: 4px; + letter-spacing: 0.3em; +} + +.phortune-invoice-status { + margin-bottom: 24px; +} + +.phortune-invoice-status .phui-info-view { + margin: 0; +} + +.phortune-invoice-view .phui-box.phui-object-box { + margin-bottom: 24px; +} + +.phortune-invoice-footer { + color: {$lightgreytext}; + margin: 48px 0 64px; + text-align: center; +} + +.phortune-invoice-footer strong { + color: #000; +}