Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add price override in react delivery form #4860

Open
wants to merge 54 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
05a0697
feat: allow to pass custom serialization groups via URL
r0xsh Jan 21, 2025
fd4e5a5
start modify barecodes modal
CamilleNerriere Jan 22, 2025
0064f49
clean: inject auth in delivery form
Atala Jan 22, 2025
9c3f4fb
add barecodes in new delivery form/ now barecodes Modal fetch deliver…
CamilleNerriere Jan 22, 2025
55b7307
remove console.log
CamilleNerriere Jan 22, 2025
cf0ad48
add translations
CamilleNerriere Jan 22, 2025
d93c78a
create component override price and begining of its logic for new del…
CamilleNerriere Jan 22, 2025
fdc6094
fix css
CamilleNerriere Jan 22, 2025
c5a01db
add override price in edit mode
CamilleNerriere Jan 22, 2025
46cc535
fix hide new price if new price equal 0
CamilleNerriere Jan 22, 2025
3a4c5a7
add style
CamilleNerriere Jan 22, 2025
512e362
hide tags for stores
CamilleNerriere Jan 22, 2025
0a1bd7e
add translations
CamilleNerriere Jan 22, 2025
2d7d093
create VAT price converter widget
CamilleNerriere Jan 23, 2025
5e09e38
add margin
CamilleNerriere Jan 23, 2025
bb46a8d
feat: take into account arbitrary price on delivery creation
Atala Jan 23, 2025
53bad10
fix css, add remove override data
CamilleNerriere Jan 23, 2025
03e1381
Merge branch 'feat/deliveryform-override-price' of https://github.com…
CamilleNerriere Jan 23, 2025
7544262
add validation
CamilleNerriere Jan 23, 2025
488c887
add admin restriction to override price and refactor component
CamilleNerriere Jan 24, 2025
7547c94
add name in address select component
CamilleNerriere Jan 24, 2025
de69878
clean: serialize prefillPickupAddress for store entity
Atala Jan 24, 2025
55b5473
start add store address preselected
CamilleNerriere Jan 24, 2025
f75b104
merge commit
CamilleNerriere Jan 24, 2025
f46b614
feat: send multiDropEnabled parameter
Atala Jan 24, 2025
722f3c4
add autofilled store address if option selected
CamilleNerriere Jan 24, 2025
71b3dc6
merge commit
CamilleNerriere Jan 24, 2025
2c2965f
fix calculate price
CamilleNerriere Jan 24, 2025
53fa7d2
add larger area to click for toggle and override price checkbox
CamilleNerriere Jan 24, 2025
edc9f36
set default timeSlots in DateRangePicker with 10' range
CamilleNerriere Jan 24, 2025
3622567
unify color and add restriction for multidropoff
CamilleNerriere Jan 24, 2025
7b01640
remove console.log
CamilleNerriere Jan 24, 2025
30b9903
Merge branch 'master' into feat/deliveryform-override-price
Atala Jan 28, 2025
932aaba
clean: simplify ShowPrice component
Atala Jan 28, 2025
8558f26
clean: factorize ShowPrice component
Atala Jan 28, 2025
f34ccd1
clean: handle price edition in beta form
Atala Jan 28, 2025
faced90
feat: add serialization of deliveryPrice
Atala Jan 28, 2025
61a1d15
Merge branch 'master' into feat/deliveryform-override-price
Atala Jan 28, 2025
19e6417
Merge branch 'master' into feat/deliveryform-override-price
Atala Jan 29, 2025
0eb4117
feat: add cypress test for beta form new delivery
Atala Jan 29, 2025
e79fb86
Merge branch 'master' into feat/deliveryform-override-price
vladimir-8 Jan 31, 2025
0a73058
WIP
Atala Jan 31, 2025
26a78f8
fix: use ArbitraryPrice as input
Atala Feb 7, 2025
3d1d9b4
Merge branch 'master' into feat/deliveryform-override-price
Atala Feb 7, 2025
a599b7a
clean: small css fixes
Atala Feb 7, 2025
96b1beb
fix: tax rate in tests
Atala Feb 7, 2025
7b10b09
feat: debounce call to calculate endpoint
Atala Feb 7, 2025
49efaba
enable all cypress tests
Atala Feb 7, 2025
7d79b98
fix: translate calculate price error message
Atala Feb 7, 2025
c2a9a54
feat: throw if user is admin and price could not be calculated
Atala Feb 7, 2025
b3bb9f0
feat: fix behat tests
Atala Feb 7, 2025
a9d9468
Merge branch 'master' into feat/deliveryform-override-price
Atala Feb 7, 2025
f402ce2
fix: arbitrary price can be null
Atala Feb 7, 2025
ecf9e7f
fix: tests
Atala Feb 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 97 additions & 127 deletions assets/react/controllers/Delivery/DeliveryForm.jsx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it always be excl. VAT and then incl. VAT?

Screenshot 2025-02-10 at 15 38 46

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have an issue when I select Pre-fill automatically pickup address. It pre-fills the same address in both pickup and dropoff:

Screenshot 2025-02-10 at 15 43 42

The same happens when I open an existing delivery:

Screenshot 2025-02-10 at 15 45 25

Screenshot 2025-02-10 at 15 45 32

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the catch i ll fix this

Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React, { useCallback, useEffect, useRef, useState } from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import { Button } from 'antd'
import { Formik, Form, FieldArray } from 'formik'
import Task from '../../../../js/app/components/delivery-form/Task.js'
import { antdLocale } from '../../../../js/app/i18n'
import { ConfigProvider } from 'antd'
import moment from 'moment'
import { money } from '../../controllers/Incident/utils.js'

import Map from '../../../../js/app/components/delivery-form/Map.js'
import Spinner from '../../../../js/app/components/core/Spinner.js'
import _ from 'lodash'

import BarcodesModal from '../BarcodesModal.jsx'
import ShowPrice from '../../../../js/app/components/delivery-form/ShowPrice.js'
import Task from '../../../../js/app/components/delivery-form/Task.js'

import { PhoneNumberUtil } from 'google-libphonenumber'
import { getCountry } from '../../../../js/app/i18n'
Expand All @@ -18,8 +18,8 @@ import { parsePhoneNumberFromString } from 'libphonenumber-js'


import "./DeliveryForm.scss"
import BarcodesModal from '../BarcodesModal.jsx'
import OverridePrice from '../../../../js/app/components/delivery-form/OverridePrice.js'
import _ from 'lodash'


/** used in case of phone validation */
const phoneUtil = PhoneNumberUtil.getInstance();
Expand Down Expand Up @@ -107,6 +107,8 @@ const baseURL = location.protocol + '//' + location.host

export default function ({ storeId, deliveryId, order }) {

console.log(order)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove?

// This variable is used to test the store role and restrictions. We need to have it passed as prop to make it work.
const isAdmin = true

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to call isDispatcher

Expand All @@ -124,18 +126,20 @@ export default function ({ storeId, deliveryId, order }) {
tasks: [
pickupSchema,
dropoffSchema,
]
],
})
const [isLoading, setIsLoading] = useState(Boolean(deliveryId))
const [overridePrice, setOverridePrice] = useState(false)
const [priceLoading, setPriceLoading] = useState(false)


let deliveryPrice

if (order) {
if (deliveryId && order) {
const orderInfos = JSON.parse(order)
deliveryPrice = {exVAT: +orderInfos.total, VAT: +orderInfos.total - +orderInfos.taxTotal,}
}


const { t } = useTranslation()

const validate = (values) => {
Expand Down Expand Up @@ -170,12 +174,13 @@ export default function ({ storeId, deliveryId, order }) {
}
}

return Object.keys(errors.tasks).length > 0 ? errors : {}
}

// Could not figure out why, but sometimes Formik "re-renders" even if the values are the same.
// so i store a ref to previous values to avoid re-calculating the price.
const previousValues = useRef(initialValues);
if (overridePrice && !values.variantName) {
errors.variantName = t("DELIVERY_FORM_ERROR_VARIANT_NAME")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behaviour in the old form is to allow an empty product name (as a way to remove the custom name and generate a default name). I think it's good to keep that behaviour at least

Screenshot 2025-02-10 at 14 46 36

}

return Object.keys(errors.tasks).length > 0 || errors.variantName ? errors : {};
}

useEffect(() => {
const deliveryURL = `${baseURL}/api/deliveries/${deliveryId}?groups=barcode,address,delivery`
Expand Down Expand Up @@ -213,8 +218,6 @@ export default function ({ storeId, deliveryId, order }) {
}
})

previousValues.current = delivery.response

setInitialValues(delivery.response)
setTrackingLink(delivery.response.trackingUrl)
setAddresses(addresses.response['hydra:member'])
Expand Down Expand Up @@ -261,11 +264,23 @@ export default function ({ storeId, deliveryId, order }) {
const createOrEditADelivery = async (deliveryId) => {
const url = getUrl(deliveryId);
const method = deliveryId ? 'put' : 'post';
return await httpClient[method](url, {

let data = {
store: storeDeliveryInfos['@id'],
tasks: values.tasks
});
}

if (values.variantIncVATPrice && values.variantName) {
data = {
...data,
arbitraryPrice: {
variantPrice: values.variantIncVATPrice,
variantName: values.variantName
}
}
}

return await httpClient[method](url, data);
}

const {response, error} = await createOrEditADelivery(deliveryId)
Expand Down Expand Up @@ -299,73 +314,50 @@ export default function ({ storeId, deliveryId, order }) {
}, [storeDeliveryInfos])


const getPrice = (values) => {
const getPrice = _.debounce(
(values) => {

if (_.isEqual(previousValues.current, values)) {
return
}
const tasksCopy = structuredClone(values.tasks)
const tasksWithoutId = tasksCopy.map(task => {
if (task["@id"]) {
delete task["@id"]
}
return task
})

const infos = {
store: storeDeliveryInfos["@id"],
tasks: tasksWithoutId,
};

const calculatePrice = async () => {

previousValues.current = values
setPriceLoading(true)

const tasksCopy = structuredClone(values.tasks)
const tasksWithoutId = tasksCopy.map(task => {
if (task["@id"]) {
delete task["@id"]
const url = `${baseURL}/api/retail_prices/calculate`
const { response, error } = await httpClient.post(url, infos)

if (error) {
setPriceError({ isPriceError: true, priceErrorMessage: error.response.data['hydra:description'] })
setCalculatePrice(0)
}
return task
})

let packages = []

for (const task of values.tasks) {
if (task.packages && task.type ==="DROPOFF") {
packages.push(...task.packages)
}
}

const mergedPackages = _(packages)
.groupBy('type')
.map((items, type) => ({
type,
quantity: _.sumBy(items, 'quantity'),
}))
.value()
if (response) {
setCalculatePrice(response)
setPriceError({ isPriceError: false, priceErrorMessage: ' ' })
}

let totalWeight = 0
setPriceLoading(false)

for (const task of values.tasks) {
if (task.weight && task.type ==="DROPOFF") {
totalWeight+= task.weight
}
}

const infos = {
store: storeDeliveryInfos["@id"],
weight: totalWeight,
packages: mergedPackages,
tasks: tasksWithoutId,
};

const calculatePrice = async () => {
const url = `${baseURL}/api/retail_prices/calculate`
const { response, error } = await httpClient.post(url, infos)

if (error) {
setPriceError({ isPriceError: true, priceErrorMessage: error.response.data['hydra:description'] })
setCalculatePrice(0)
}

if (response) {
setCalculatePrice(response)
setPriceError({ isPriceError: false, priceErrorMessage: ' ' })

if (values.tasks.every(task => task.address.streetAddress)) {
calculatePrice()
}

}
if (values.tasks.every(task => task.address.streetAddress)) {
calculatePrice()
}

}
},
1000
)

return (
isLoading ?
Expand All @@ -384,8 +376,8 @@ export default function ({ storeId, deliveryId, order }) {
{({ values, isSubmitting }) => {

useEffect(() => {
getPrice(values)
}, [values]);
if(!overridePrice && !deliveryId) getPrice(values)
}, [values.tasks, overridePrice, deliveryId]);

return (
<Form >
Expand Down Expand Up @@ -433,18 +425,27 @@ export default function ({ storeId, deliveryId, order }) {
addresses={addresses}
storeId={storeId}
storeDeliveryInfos={storeDeliveryInfos}
onAdd={arrayHelpers.push}
dropoffSchema={dropoffSchema}
onRemove={arrayHelpers.remove}
showRemoveButton={originalIndex > 1}
showAddButton={originalIndex === values.tasks.length - 1}
packages={storePackages}
isAdmin={isAdmin}
tags={tags}
/>
</div>
);
})}

{storeDeliveryInfos.multiDropEnabled ? <div
className="new-order__dropoffs__add p-4 border mb-4">
<p>{t('DELIVERY_FORM_MULTIDROPOFF')}</p>
<Button
disabled={false}
onClick={() => {
arrayHelpers.push(dropoffSchema)
}}>
{t('DELIVERY_FORM_ADD_DROPOFF')}
</Button>
</div> : null}
</div>
</div>
)}
Expand Down Expand Up @@ -472,57 +473,26 @@ export default function ({ storeId, deliveryId, order }) {
/>
</div>

<div className='order-informations__total-price border-top border-bottom pt-3 pb-3 mb-4'>
{deliveryPrice ?
<div className='mb-4'>
<div className='font-weight-bold mb-2'>{ t("DELIVERY_FORM_OLD_PRICE")}</div>
<div>{money(deliveryPrice.exVAT)} {t("DELIVERY_FORM_TOTAL_VAT")}</div>
<div>{money(deliveryPrice.VAT)} {t("DELIVERY_FORM_TOTAL_EX_VAT")}</div>
<div className='mt-2 text-danger'>Editing price is not yet available in beta version.</div>
</div> : null }

{!deliveryId ?
<>
<div className='font-weight-bold mb-2'>{deliveryId ? t("DELIVERY_FORM_NEW_PRICE") : t("DELIVERY_FORM_TOTAL_PRICE")} </div>
<div>
{calculatedPrice.amount
?
<div>
<div className='mb-1'>
{money(calculatedPrice.amount)} {t("DELIVERY_FORM_TOTAL_VAT")}
</div>
<div>
{money(calculatedPrice.amount - calculatedPrice.tax.amount)} {t("DELIVERY_FORM_TOTAL_EX_VAT")}
</div>
</div>
:
<div>
<div className='mb-1'>
{money(0)} {t("DELIVERY_FORM_TOTAL_VAT")}
</div>
<div>
{money(0)} {t("DELIVERY_FORM_TOTAL_EX_VAT")}
</div>
</div>
}
<OverridePrice/>
</div>
{priceError.isPriceError ?
<div className="alert alert-danger mt-4" role="alert">
{priceError.priceErrorMessage}
</div>
: null}
</> :
null
}

<div className='order-informations__total-price border-top border-bottom pt-3 mb-4'>
<ShowPrice
isAdmin={isAdmin}
deliveryId={deliveryId}
deliveryPrice={deliveryPrice}
calculatedPrice={calculatedPrice}
setCalculatePrice={setCalculatePrice}
priceError={priceError}
setOverridePrice={setOverridePrice}
overridePrice={overridePrice}
priceLoading={priceLoading}
/>
</div>

<div className='order-informations__complete-order'>
<Button
type="primary"
style={{ height: '2.5em' }}
htmlType="submit" disabled={isSubmitting || deliveryId && isAdmin === false}>
htmlType="submit"
disabled={isSubmitting || deliveryId && isAdmin === false}>
{t("DELIVERY_FORM_SUBMIT")}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should also be disabled when priceLoading is true, as I understand #4854

</Button>
</div>
Expand Down
19 changes: 18 additions & 1 deletion assets/react/controllers/Delivery/DeliveryForm.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
flex-wrap: wrap;
gap: 2em;
padding: 2em;

}

.new-order {
Expand All @@ -16,6 +17,12 @@
&__pickups, &__dropoffs {
width: 47%;
}

&__dropoffs__add{
display: flex;
justify-content: space-between;
align-items: center;
}
}

.delivery-form {
Expand All @@ -26,7 +33,17 @@
align-self: start;
position: sticky;
top:0;
min-width: 30%;
width: 30%;

&__tracking{
background-color: #f3f2f2f8;
border-color: #dbdada;
color: #23527C;
}

&__complete-order button{
background-color: #337AB7;
}
}


Expand Down
Loading
Loading