1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-18 07:52:23 +01:00

Merge branch 'product-store_order' into product-store

This commit is contained in:
Du Peng 2022-09-19 19:32:36 +02:00
commit eebf2bcb43
15 changed files with 65 additions and 20 deletions

View File

@ -40,7 +40,7 @@ class API::ProductsController < API::ApiController
def destroy
authorize @product
@product.destroy
ProductService.destroy(@product)
head :no_content
end

View File

@ -0,0 +1,3 @@
# Raised when delete a product if this product has used in order
class CannotDeleteProductError < StandardError
end

View File

@ -0,0 +1,5 @@
# frozen_string_literal: true
# Raised when the item's amount != product's amount
class Cart::ItemAmountError < StandardError
end

View File

@ -0,0 +1,5 @@
# frozen_string_literal: true
# Raised when the item's quantity < product's quantity min
class Cart::QuantityMinError < StandardError
end

View File

@ -59,7 +59,7 @@ export const FormInput = <TFieldValues extends FieldValues, TInputType>({ id, re
{...register(id as FieldPath<TFieldValues>, {
...rules,
valueAsDate: type === 'date',
setValueAs: v => (v === null && nullable) ? null : (type === 'number' ? parseInt(v, 10) : v),
setValueAs: v => (v === null && nullable) ? null : (type === 'number' ? parseFloat(v) : v),
value: defaultValue as FieldPathValue<TFieldValues, FieldPath<TFieldValues>>,
onChange: (e) => { handleChange(e); }
})}

View File

@ -280,7 +280,7 @@ export const ProductForm: React.FC<ProductFormProps> = ({ product, title, onSucc
<FormInput id="amount"
type="number"
register={register}
rules={{ required: true, min: 0.01 }}
rules={{ required: true, min: 0 }}
step={0.01}
formState={formState}
label={t('app.admin.store.product_form.price')} />

View File

@ -79,12 +79,10 @@ export const ProductItem: React.FC<ProductItemProps> = ({ product, onEdit, onDel
<span>{t('app.admin.store.product_item.stock.external')}</span>
<p>{product.stock.external}</p>
</div>
{product.amount &&
<div className='price'>
<p>{FormatLib.price(product.amount)}</p>
<span>/ {t('app.admin.store.product_item.unit')}</span>
</div>
}
<div className='price'>
<p>{FormatLib.price(product.amount || 0)}</p>
<span>/ {t('app.admin.store.product_item.unit')}</span>
</div>
</div>
<div className='actions'>
<div className='manage'>

View File

@ -81,12 +81,10 @@ export const StoreProductItem: React.FC<StoreProductItemProps> = ({ product, car
<img src={productImageUrl(product)} alt='' />
</div>
<p className="name">{product.name}</p>
{product.amount &&
<div className='price'>
<p>{FormatLib.price(product.amount)}</p>
<span>/ {t('app.public.store_product_item.unit')}</span>
</div>
}
<div className='price'>
<p>{FormatLib.price(product.amount || 0)}</p>
<span>/ {t('app.public.store_product_item.unit')}</span>
</div>
<FabStateLabel status={statusColor(product)}>
{productStockStatus(product)}
</FabStateLabel>

View File

@ -180,7 +180,7 @@ export const StoreProduct: React.FC<StoreProductProps> = ({ productSlug, current
{productStockStatus(product)}
</FabStateLabel>
<div className='price'>
<p>{FormatLib.price(product.amount)} <sup>TTC</sup></p>
<p>{FormatLib.price(product.amount || 0)} <sup>TTC</sup></p>
<span>/ {t('app.public.store_product_item.unit')}</span>
</div>
{product.stock.external > (product.quantity_min || 1) &&

View File

@ -20,7 +20,8 @@ class Product < ApplicationRecord
accepts_nested_attributes_for :product_stock_movements, allow_destroy: true, reject_if: :all_blank
validates :name, :slug, presence: true
validates :amount, numericality: { greater_than: 0, allow_nil: true }
validates :slug, uniqueness: true
validates :amount, numericality: { greater_than_or_equal_to: 0, allow_nil: true }
scope :active, -> { where(is_active: true) }

View File

@ -7,6 +7,7 @@ class ProductCategory < ApplicationRecord
friendly_id :name, use: :slugged
validates :name, :slug, presence: true
validates :slug, uniqueness: true
belongs_to :parent, class_name: 'ProductCategory'
has_many :children, class_name: 'ProductCategory', foreign_key: :parent_id

View File

@ -11,7 +11,7 @@ class Cart::AddItemService
quantity = orderable.quantity_min > quantity.to_i && item.nil? ? orderable.quantity_min : quantity.to_i
if item.nil?
item = order.order_items.new(quantity: quantity, orderable: orderable, amount: orderable.amount)
item = order.order_items.new(quantity: quantity, orderable: orderable, amount: orderable.amount || 0)
else
item.quantity += quantity.to_i
end

View File

@ -7,9 +7,13 @@ class Checkout::PaymentService
include Payments::PaymentConcern
def payment(order, operator, coupon_code, payment_id = '')
raise Cart::InactiveProductError unless Orders::OrderService.new.all_products_is_active?(order)
raise Cart::OutStockError unless Orders::OrderService.new.in_stock?(order, 'external')
raise Cart::InactiveProductError unless Orders::OrderService.new.all_products_is_active?(order)
raise Cart::QuantityMinError unless Orders::OrderService.new.greater_than_quantity_min?(order)
raise Cart::ItemAmountError unless Orders::OrderService.new.item_amount_not_equal?(order)
CouponService.new.validate(coupon_code, order.statistic_profile.user.id)

View File

@ -58,6 +58,20 @@ class Orders::OrderService
true
end
def greater_than_quantity_min?(order)
order.order_items.each do |item|
return false if item.quantity < item.orderable.quantity_min
end
true
end
def item_amount_not_equal?(order)
order.order_items.each do |item|
return false if item.amount != item.orderable.amount
end
true
end
def all_products_is_active?(order)
order.order_items.each do |item|
return false unless item.orderable.is_active

View File

@ -55,7 +55,8 @@ class ProductService
def create(product_params, stock_movement_params = [])
product = Product.new(product_params)
product.amount = amount_multiplied_by_hundred(product_params[:amount])
update(product, product_params, stock_movement_params)
update_stock(product, stock_movement_params)
product
end
def update(product, product_params, stock_movement_params = [])
@ -64,5 +65,20 @@ class ProductService
update_stock(product, stock_movement_params)
product
end
def destroy(product)
used_in_order = OrderItem.joins(:order).where.not('orders.state' => 'cart')
.exists?(orderable: product)
raise CannotDeleteProductError if used_in_order
ActiveRecord::Base.transaction do
orders_with_product = Order.joins(:order_items).where(state: 'cart').where('order_items.orderable': product)
orders_with_product.each do |order|
::Cart::RemoveItemService.new.call(order, product)
end
product.destroy
end
end
end
end