gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-merchant-backoffice] 03/05: joint inventory product and custom pr


From: gnunet
Subject: [taler-merchant-backoffice] 03/05: joint inventory product and custom products
Date: Thu, 24 Jun 2021 14:30:12 +0200

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository merchant-backoffice.

commit 520e1d886faeeda6dd56ca5451baf1e6329ab1ea
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Jun 22 12:31:30 2021 -0300

    joint inventory product and custom products
---
 packages/frontend/src/components/form/useField.tsx |  4 --
 .../product/InventoryProductForm.stories.tsx       | 58 ++++++++++++++++
 .../product}/InventoryProductForm.tsx              | 31 ++++++---
 .../product}/NonInventoryProductForm.tsx           | 30 ++++----
 .../src/components/product/ProductList.tsx         |  7 +-
 .../paths/instance/orders/create/CreatePage.tsx    | 80 +++++++++-------------
 6 files changed, 128 insertions(+), 82 deletions(-)

diff --git a/packages/frontend/src/components/form/useField.tsx 
b/packages/frontend/src/components/form/useField.tsx
index a04be70..8479d7a 100644
--- a/packages/frontend/src/components/form/useField.tsx
+++ b/packages/frontend/src/components/form/useField.tsx
@@ -49,10 +49,6 @@ export function useField<T>(name: keyof T): Use<T[typeof 
name]> {
   const initial = readField(initialObject, String(name))
   const isDirty = value !== initial
   const hasError = readField(errors, String(name))
-  if (name == 'pricing.order_price') {
-
-    console.log(value, initial, value === initial)
-  }
   return {
     error: isDirty ? hasError : undefined,
     required: !isDirty && hasError,
diff --git 
a/packages/frontend/src/components/product/InventoryProductForm.stories.tsx 
b/packages/frontend/src/components/product/InventoryProductForm.stories.tsx
new file mode 100644
index 0000000..6504d85
--- /dev/null
+++ b/packages/frontend/src/components/product/InventoryProductForm.stories.tsx
@@ -0,0 +1,58 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
+
+import { h, VNode, FunctionalComponent } from 'preact';
+import { InventoryProductForm as TestedComponent } from 
'./InventoryProductForm';
+
+
+export default {
+  title: 'Components/Product/Add',
+  component: TestedComponent,
+  argTypes: {
+    onAddProduct: { action: 'onAddProduct' },
+  },
+};
+
+function createExample<Props>(Component: FunctionalComponent<Props>, props: 
Partial<Props>) {
+  const r = (args: any) => <Component {...args} />
+  r.args = props
+  return r
+}
+
+export const WithASimpleList = createExample(TestedComponent, {
+  inventory:[{
+    id: 'this id',
+    description: 'this is the description',
+  } as any]
+});
+
+export const WithAProductSelected = createExample(TestedComponent, {
+  inventory:[],
+  currentProducts: {
+    thisid: {
+      quantity: 1,
+      product: {
+        id: 'asd',
+        description: 'asdsadsad',
+      } as any
+    }
+  }
+});
diff --git 
a/packages/frontend/src/paths/instance/orders/create/InventoryProductForm.tsx 
b/packages/frontend/src/components/product/InventoryProductForm.tsx
similarity index 76%
rename from 
packages/frontend/src/paths/instance/orders/create/InventoryProductForm.tsx
rename to packages/frontend/src/components/product/InventoryProductForm.tsx
index dde4505..6f05f26 100644
--- 
a/packages/frontend/src/paths/instance/orders/create/InventoryProductForm.tsx
+++ b/packages/frontend/src/components/product/InventoryProductForm.tsx
@@ -15,12 +15,12 @@
  */
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import { FormProvider, FormErrors } from 
"../../../../components/form/FormProvider";
-import { InputNumber } from "../../../../components/form/InputNumber";
-import { InputSearchProduct } from 
"../../../../components/form/InputSearchProduct";
-import { MerchantBackend, WithId } from "../../../../declaration";
-import { Translate, useTranslator } from "../../../../i18n";
-import { ProductMap } from "./CreatePage";
+import { FormProvider, FormErrors } from "../form/FormProvider";
+import { InputNumber } from "../form/InputNumber";
+import { InputSearchProduct } from "../form/InputSearchProduct";
+import { MerchantBackend, WithId } from "../../declaration";
+import { Translate, useTranslator } from "../../i18n";
+import { ProductMap } from "../../paths/instance/orders/create/CreatePage";
 
 type Form = {
   product: MerchantBackend.Products.ProductDetail & WithId,
@@ -77,10 +77,19 @@ export function InventoryProductForm({ currentProducts, 
onAddProduct, inventory
   }
 
   return <FormProvider<Form> errors={errors} object={state} 
valueHandler={setState}>
-    <InputSearchProduct selected={state.product} onChange={(p) => setState(v 
=> ({ ...v, product: p }))} products={inventory}  />
-    { state.product && !productWithInfiniteStock && <InputNumber<Form> 
name="quantity" label={i18n`Quantity`} tooltip={i18n`how many products will be 
added`} /> }
-    <div class="buttons is-right mt-5">
-      <button class="button is-success" disabled={!state.product} 
onClick={submit}><Translate>Add</Translate></button>
-    </div>
+    <InputSearchProduct selected={state.product} onChange={(p) => setState(v 
=> ({ ...v, product: p }))} products={inventory} />
+    { state.product && <div class="columns mt-5">
+      <div class="column is-four-fifths">
+        {!productWithInfiniteStock &&
+          <InputNumber<Form> name="quantity" label={i18n`Quantity`} 
tooltip={i18n`how many products will be added`} />
+        }
+      </div>
+      <div class="column">
+        <div class="buttons is-right">
+          <button class="button is-success" 
onClick={submit}><Translate>Add</Translate></button>
+        </div>
+      </div>
+    </div> }
+
   </FormProvider>
 }
diff --git 
a/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx
 b/packages/frontend/src/components/product/NonInventoryProductForm.tsx
similarity index 85%
rename from 
packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx
rename to packages/frontend/src/components/product/NonInventoryProductForm.tsx
index 34e5213..3ba4764 100644
--- 
a/packages/frontend/src/paths/instance/orders/create/NonInventoryProductForm.tsx
+++ b/packages/frontend/src/components/product/NonInventoryProductForm.tsx
@@ -15,21 +15,20 @@
  */
 import { Fragment, h, VNode } from "preact";
 import { useCallback, useEffect, useState } from "preact/hooks";
-import { FormProvider, FormErrors } from 
"../../../../components/form/FormProvider";
-import { Input } from "../../../../components/form/Input";
-import { InputCurrency } from "../../../../components/form/InputCurrency";
-import { InputImage } from "../../../../components/form/InputImage";
-import { InputNumber } from "../../../../components/form/InputNumber";
-import { InputTaxes } from "../../../../components/form/InputTaxes";
-import { ConfirmModal } from "../../../../components/modal";
-import { MerchantBackend } from "../../../../declaration";
-import { useListener } from "../../../../hooks/listener";
-
+import * as yup from 'yup';
+import { FormErrors, FormProvider } from "../form/FormProvider";
+import { Input } from "../form/Input";
+import { InputCurrency } from "../form/InputCurrency";
+import { InputImage } from "../form/InputImage";
+import { InputNumber } from "../form/InputNumber";
+import { InputTaxes } from "../form/InputTaxes";
+import { MerchantBackend } from "../../declaration";
+import { useListener } from "../../hooks/listener";
+import { Translate, useTranslator } from "../../i18n";
 import {
   NonInventoryProductSchema as schema
-} from '../../../../schemas';
-import * as yup from 'yup';
-import { Translate, useTranslator } from "../../../../i18n";
+} from '../../schemas';
+
 
 type Entity = MerchantBackend.Product
 
@@ -63,11 +62,9 @@ export function NonInventoryProductFrom({ productToEdit, 
onAddProduct }: Props):
 
   const i18n = useTranslator()
 
-  console.log('submit form', submitForm)
-
   return <Fragment>
     <div class="buttons">
-      <button class="button is-success" onClick={() => 
setShowCreateProduct(true)} ><Translate>add product</Translate></button>
+      <button class="button is-success" onClick={() => 
setShowCreateProduct(true)} ><Translate>add custom product</Translate></button>
     </div>
     {showCreateProduct && <div class="modal is-active">
       <div class="modal-background " onClick={() => 
setShowCreateProduct(false)} />
@@ -125,7 +122,6 @@ export function ProductForm({ onSubscribe, initial }: 
ProductProps) {
   const hasErrors = Object.keys(errors).some(k => (errors as any)[k] !== 
undefined)
 
   useEffect(() => {
-    console.log('has errors', hasErrors)
     onSubscribe(hasErrors ? undefined : submit)
   }, [submit, hasErrors])
 
diff --git a/packages/frontend/src/components/product/ProductList.tsx 
b/packages/frontend/src/components/product/ProductList.tsx
index b1486d6..c343b75 100644
--- a/packages/frontend/src/components/product/ProductList.tsx
+++ b/packages/frontend/src/components/product/ProductList.tsx
@@ -15,9 +15,9 @@
  */
 import { h, VNode } from "preact"
 import { MerchantBackend } from "../../declaration"
-import { multiplyPrice } from "../../utils/amount"
+import { Amounts } from "@gnu-taler/taler-util";
 import emptyImage from "../../assets/empty.png";
-import { Translate, useTranslator } from "../../i18n";
+import { Translate } from "../../i18n";
 
 interface Props {
   list: MerchantBackend.Product[],
@@ -28,7 +28,6 @@ interface Props {
   }[]
 }
 export function ProductList({ list, actions = [] }: Props): VNode {
-  const i18n = useTranslator()
   return <div class="table-container">
     <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
       <thead>
@@ -52,7 +51,7 @@ export function ProductList({ list, actions = [] }: Props): 
VNode {
               {entry.quantity === 0 ? '--' : `${entry.quantity} ${entry.unit}`}
             </td>
             <td >{entry.price}</td>
-            <td >{multiplyPrice(entry.price, entry.quantity)}</td>
+            <td 
>{Amounts.stringify(Amounts.mult(Amounts.parseOrThrow(entry.price), 
entry.quantity).amount)}</td>
             <td class="is-actions-cell right-sticky">
               {actions.map((a, i) => {
                 return <div key={i} class="buttons is-right">
diff --git a/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx 
b/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
index e4d19a8..f6550aa 100644
--- a/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/instance/orders/create/CreatePage.tsx
@@ -20,7 +20,7 @@
 */
 
 import { add, isBefore, isFuture } from "date-fns";
-import { AmountJson, Amounts } from "@gnu-taler/taler-util";
+import { Amounts } from "@gnu-taler/taler-util";
 import { Fragment, h, VNode } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import { FormProvider, FormErrors } from 
"../../../../components/form/FormProvider";
@@ -35,8 +35,8 @@ import { Duration, MerchantBackend, WithId } from 
"../../../../declaration";
 import { Translate, useTranslator } from "../../../../i18n";
 import { OrderCreateSchema as schema } from '../../../../schemas/index';
 import { rate } from "../../../../utils/amount";
-import { InventoryProductForm } from "./InventoryProductForm";
-import { NonInventoryProductFrom } from "./NonInventoryProductForm";
+import { InventoryProductForm } from 
"../../../../components/product/InventoryProductForm";
+import { NonInventoryProductFrom } from 
"../../../../components/product/NonInventoryProductForm";
 
 interface Props {
   onCreate: (d: MerchantBackend.Orders.PostOrderRequest) => void;
@@ -113,6 +113,9 @@ const stringIsValidJSON = (value: string) => {
   }
 }
 
+function undefinedIfEmpty<T>(obj: T): T | undefined {
+  return Object.keys(obj).some(k => (obj as any)[k] !== undefined) ? obj : 
undefined
+}
 
 export function CreatePage({ onCreate, onBack, instanceConfig, 
instanceInventory }: Props): VNode {
   const config = useConfigContext()
@@ -124,19 +127,15 @@ export function CreatePage({ onCreate, onBack, 
instanceConfig, instanceInventory
 
   const i18n = useTranslator()
 
-  function check<T>(obj: T): T | undefined {
-    return Object.keys(obj).some(k => (obj as any)[k] !== undefined) ? obj : 
undefined
-  }
-
   const errors: FormErrors<Entity> = {
-    pricing: {
-      summary: !value.pricing?.summary ? i18n`required`:undefined,
-      order_price: !value.pricing?.order_price ? i18n`required`: (
+    pricing: undefinedIfEmpty({
+      summary: !value.pricing?.summary ? i18n`required` : undefined,
+      order_price: !value.pricing?.order_price ? i18n`required` : (
         (Amounts.parse(value.pricing.order_price)?.value || 0) <= 0 ? 
i18n`must be greater than 0` : undefined
       )
-    },
+    }),
     extra: value.extra && !stringIsValidJSON(value.extra) ? i18n`not a valid 
json` : undefined,
-    payments: check({
+    payments: undefinedIfEmpty({
       refund_deadline: !value.payments?.refund_deadline ? i18n`required` : (
         !isFuture(value.payments.refund_deadline) ? i18n`should be in the 
future` : (
           value.payments.pay_deadline && value.payments.refund_deadline && 
isBefore(value.payments.refund_deadline, value.payments.pay_deadline) ?
@@ -230,21 +229,24 @@ export function CreatePage({ onCreate, onBack, 
instanceConfig, instanceInventory
   const hasProducts = inventoryList.length > 0 || productList.length > 0
   const totalPrice = Amounts.add(totalPriceInventory, totalPriceProducts)
 
+  const totalAsString = Amounts.stringify(totalPrice.amount);
+  const allProducts = productList.concat(inventoryList.map(asProduct))
+
   useEffect(() => {
     valueHandler(v => {
       return ({
         ...v, pricing: {
-          ...v.pricing!,
-          // products_price: (Amounts.isZero(totalPrice.amount) ? undefined : 
Amounts.stringify(totalPrice.amount))!,
-          // order_price: (Amounts.isZero(totalPrice.amount) ? undefined : 
Amounts.stringify(totalPrice.amount))!,
+          ...v.pricing,
+          products_price: hasProducts ? totalAsString : undefined,
+          order_price: hasProducts ? totalAsString : undefined,
           // products_price: Amounts.stringify(totalPrice.amount),
           // order_price: Amounts.stringify(totalPrice.amount),
         }
       })
     })
-  }, [hasProducts, totalPrice])
+  }, [hasProducts, totalAsString])
 
-  const discountOrRise = rate(value.pricing?.order_price || 
`${config.currency}:0`, Amounts.stringify(totalPrice.amount))
+  const discountOrRise = rate(value.pricing?.order_price || 
`${config.currency}:0`, totalAsString)
 
   // useEffect(() => {
   //   valueHandler(v => {
@@ -263,54 +265,40 @@ export function CreatePage({ onCreate, onBack, 
instanceConfig, instanceInventory
         <div class="column" />
         <div class="column is-four-fifths">
 
-          <InputGroup name="inventory_products" label={i18n`Manage products 
from inventory in order`} alternative={
-            inventoryList.length > 0 && <p>
-              {/* // FIXME: translating plural singular */}
-              {inventoryList.length} products
-              with a total price of {Amounts.stringify(totalPriceInventory)}.
+          {/* // FIXME: translating plural singular */}
+          <InputGroup name="inventory_products" label={i18n`Manage products in 
order`} alternative={
+            allProducts.length > 0 && <p>
+              {allProducts.length} products
+              with a total price of {totalAsString}.
             </p>
-          } tooltip={i18n`Manage list of products from managed inventory 
included in the order.`}>
+          } tooltip={i18n`Manage list of products in the order.`}>
+
             <InventoryProductForm
               currentProducts={value.inventoryProducts || {}}
               onAddProduct={addProductToTheInventoryList}
               inventory={instanceInventory}
             />
 
-            {inventoryList.length > 0 &&
-              <ProductList list={inventoryList.map(asProduct)}
-                actions={[{
-                  name: i18n`Remove`,
-                  tooltip: i18n`Remove this product from the order.`,
-                  handler: (e) => 
removeProductFromTheInventoryList(e.product_id!)
-                }]}
-              />
-            }
-          </InputGroup>
-
-          <InputGroup name="products" label={i18n`Manage products outside of 
inventory in order`} alternative={
-            productList.length > 0 && <p>
-            {/* // FIXME: translating plural singular */}
-              {productList.length} products
-              with a total price of {Amounts.stringify(totalPriceProducts)}.
-            </p>
-          } tooltip={i18n`Manage list of products without inventory management 
included in the order.`}>
             <NonInventoryProductFrom productToEdit={editingProduct} 
onAddProduct={(p) => {
               setEditingProduct(undefined)
               return addNewProduct(p)
             }} />
 
-            {productList.length > 0 &&
-              <ProductList list={productList}
+            {allProducts.length > 0 &&
+              <ProductList list={allProducts}
                 actions={[{
                   name: i18n`Remove`,
                   tooltip: i18n`Remove this product from the order.`,
                   handler: (e, index) => {
-                    removeFromNewProduct(index);
-                    setEditingProduct(e);
+                    if (e.product_id) {
+                      removeProductFromTheInventoryList(e.product_id)
+                    } else {
+                      removeFromNewProduct(index);
+                      setEditingProduct(e);
+                    }
                   }
                 }]}
               />
-
             }
           </InputGroup>
 

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]