1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-19 13:54:25 +01:00

(feat) admin can set offer for product in cart

This commit is contained in:
Du Peng 2022-09-08 15:10:56 +02:00
parent 85720c31fa
commit 8d414a3172
13 changed files with 113 additions and 21 deletions

View File

@ -31,6 +31,12 @@ class API::CartController < API::ApiController
render 'api/orders/show'
end
def set_offer
authorize @current_order, policy_class: CartPolicy
@order = Cart::SetOfferService.new.call(@current_order, orderable, cart_params[:is_offered])
render 'api/orders/show'
end
private
def orderable

View File

@ -17,6 +17,6 @@ module API::OrderConcern
end
def cart_params
params.permit(:order_token, :orderable_id, :quantity, :user_id)
params.permit(:order_token, :orderable_id, :quantity, :user_id, :is_offered)
end
end

View File

@ -22,4 +22,9 @@ export default class CartAPI {
const res: AxiosResponse<Order> = await apiClient.put('/api/cart/set_quantity', { order_token: order.token, orderable_id: orderableId, quantity });
return res?.data;
}
static async setOffer (order: Order, orderableId: number, isOffered: boolean): Promise<Order> {
const res: AxiosResponse<Order> = await apiClient.put('/api/cart/set_offer', { order_token: order.token, orderable_id: orderableId, is_offered: isOffered });
return res?.data;
}
}

View File

@ -115,8 +115,12 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
/**
* Toggle product offer
*/
const onSwitch = (product, checked: boolean) => {
console.log('Offer ', product.orderable_name, ': ', checked);
const toogleProductOffer = (item) => {
return (checked: boolean) => {
CartAPI.setOffer(cart, item.orderable_id, checked).then(data => {
setCart(data);
}).catch(onError);
};
};
/**
@ -128,14 +132,50 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
}
};
/**
* Get the item total
*/
const itemAmount = (item): number => {
return item.quantity * Math.trunc(item.amount * 100) / 100;
};
/**
* return true if cart has offered item
*/
const hasOfferedItem = (): boolean => {
return cart.order_items_attributes
.filter(i => i.is_offered).length > 0;
};
/**
* Get the offered item total
*/
const offeredAmount = (): number => {
return cart.order_items_attributes
.filter(i => i.is_offered)
.map(i => i.amount)
.reduce((acc, curr) => acc + curr, 0);
.map(i => Math.trunc(i.amount * 100) * i.quantity)
.reduce((acc, curr) => acc + curr, 0) / 100;
};
/**
* Get the total amount before offered amount
*/
const totalBeforeOfferedAmount = (): number => {
return (Math.trunc(cart.total * 100) + Math.trunc(offeredAmount() * 100)) / 100;
};
/**
* Get the coupon amount
*/
const couponAmount = (): number => {
return (Math.trunc(cart.total * 100) - Math.trunc(computePriceWithCoupon(cart.total, cart.coupon) * 100)) / 100.00;
};
/**
* Get the paid total amount
*/
const paidTotal = (): number => {
return computePriceWithCoupon(cart.total, cart.coupon);
};
return (
@ -163,7 +203,7 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
</select>
<div className='total'>
<span>{t('app.public.store_cart.total')}</span>
<p>{FormatLib.price(item.quantity * item.amount)}</p>
<p>{FormatLib.price(itemAmount(item))}</p>
</div>
<FabButton className="main-action-btn" onClick={removeProductFromCart(item)}>
<i className="fa fa-trash" />
@ -175,7 +215,7 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
<span>Offer the product</span>
<Switch
checked={item.is_offered}
onChange={(checked) => onSwitch(item, checked)}
onChange={toogleProductOffer(item)}
width={40}
height={19}
uncheckedIcon={false}
@ -203,7 +243,7 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
<aside>
{cart && !cartIsEmpty() && isPrivileged() &&
<div> <MemberSelect onSelected={handleChangeMember} /></div>
<div> <MemberSelect onSelected={handleChangeMember} defaultUser={cart.user as User} /></div>
}
{cart && !cartIsEmpty() && <>
@ -211,17 +251,17 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
<h3>{t('app.public.store_cart.checkout_header')}</h3>
<span>{t('app.public.store_cart.checkout_products_COUNT', { COUNT: cart?.order_items_attributes.length })}</span>
<div className="list">
<p>{t('app.public.store_cart.checkout_products_total')} <span>{FormatLib.price(cart.total)}</span></p>
{offeredAmount() > 0 &&
<p>{t('app.public.store_cart.checkout_products_total')} <span>{FormatLib.price(totalBeforeOfferedAmount())}</span></p>
{hasOfferedItem() &&
<p className='gift'>{t('app.public.store_cart.checkout_gift_total')} <span>-{FormatLib.price(offeredAmount())}</span></p>
}
{cart.coupon && computePriceWithCoupon(cart.total, cart.coupon) !== cart.total &&
<p>{t('app.public.store_cart.checkout_coupon')} <span>{FormatLib.price(-(cart.total - computePriceWithCoupon(cart.total, cart.coupon)))}</span></p>
{cart.coupon &&
<p>{t('app.public.store_cart.checkout_coupon')} <span>-{FormatLib.price(couponAmount())}</span></p>
}
</div>
<p className='total'>{t('app.public.store_cart.checkout_total')} <span>{FormatLib.price(computePriceWithCoupon(cart.total, cart.coupon))}</span></p>
<p className='total'>{t('app.public.store_cart.checkout_total')} <span>{FormatLib.price(paidTotal())}</span></p>
</div>
<FabButton className='checkout-btn' onClick={checkout}>
<FabButton className='checkout-btn' onClick={checkout} disabled={!cart.user}>
{t('app.public.store_cart.checkout')}
</FabButton>
</>}

View File

@ -27,13 +27,20 @@ export const CouponInput: React.FC<CouponInputProps> = ({ user, amount, onChange
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<boolean>(false);
const [coupon, setCoupon] = useState<Coupon>();
const [code, setCode] = useState<string>();
useEffect(() => {
if (user && coupon) {
handleChange(coupon.code);
if (user && code) {
handleChange(code);
}
}, [user]);
useEffect(() => {
if (code) {
handleChange(code);
}
}, [amount]);
/**
* callback for validate the code
*/
@ -42,6 +49,7 @@ export const CouponInput: React.FC<CouponInputProps> = ({ user, amount, onChange
setMessages([]);
setError(false);
setCoupon(null);
setCode(value);
if (value) {
setLoading(true);
CouponAPI.validate(value, amount, user?.id).then((res) => {

View File

@ -29,6 +29,12 @@ export const MemberSelect: React.FC<MemberSelectProps> = ({ defaultUser, onSelec
}
}, []);
useEffect(() => {
if (!defaultUser && value) {
onSelected(value.value);
}
}, [defaultUser]);
/**
* search members by name
*/

View File

@ -5,9 +5,9 @@ export const computePriceWithCoupon = (price: number, coupon?: Coupon): number =
return price;
}
if (coupon.type === 'percent_off') {
return price - (price * coupon.percent_off / 100.00);
return (Math.trunc(price * 100) - (Math.trunc(price * 100) * coupon.percent_off / 100)) / 100;
} else if (coupon.type === 'amount_off' && price > coupon.amount_off) {
return price - coupon.amount_off;
return (Math.trunc(price * 100) - Math.trunc(coupon.amount_off * 100)) / 100;
}
return price;
};

View File

@ -13,4 +13,8 @@ class CartPolicy < ApplicationPolicy
record.statistic_profile_id.nil? && record.operator_profile_id.nil?
end
end
def set_offer?
user.privileged?
end
end

View File

@ -15,7 +15,7 @@ class Cart::AddItemService
else
item.quantity += quantity.to_i
end
order.total += (orderable.amount * quantity.to_i)
order.total += (item.amount * quantity.to_i)
ActiveRecord::Base.transaction do
item.save
order.save

View File

@ -7,7 +7,7 @@ class Cart::RemoveItemService
raise ActiveRecord::RecordNotFound if item.nil?
order.total -= (item.amount * item.quantity.to_i)
order.total -= (item.amount * item.quantity.to_i) unless item.is_offered
ActiveRecord::Base.transaction do
item.destroy!
order.save

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
# Provides methods for set offer to item in cart
class Cart::SetOfferService
def call(order, orderable, is_offered)
item = order.order_items.find_by(orderable: orderable)
raise ActiveRecord::RecordNotFound if item.nil?
if !item.is_offered && is_offered
order.total -= (item.amount * item.quantity)
elsif item.is_offered && !is_offered
order.total += (item.amount * item.quantity)
end
item.is_offered = is_offered
ActiveRecord::Base.transaction do
item.save
order.save
end
order.reload
end
end

View File

@ -12,7 +12,7 @@ class Cart::SetQuantityService
raise ActiveRecord::RecordNotFound if item.nil?
different_quantity = quantity.to_i - item.quantity
order.total += (orderable.amount * different_quantity)
order.total += (item.amount * different_quantity) unless item.is_offered
ActiveRecord::Base.transaction do
item.update(quantity: quantity.to_i)
order.save

View File

@ -159,6 +159,7 @@ Rails.application.routes.draw do
put 'add_item', on: :collection
put 'remove_item', on: :collection
put 'set_quantity', on: :collection
put 'set_offer', on: :collection
end
resources :checkout, only: %i[] do
post 'payment', on: :collection