mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-19 13:54:25 +01:00
(merge) branch 'origin/product-store'
This commit is contained in:
commit
0345476b5f
@ -40,7 +40,7 @@ class API::ProductsController < API::ApiController
|
||||
|
||||
def destroy
|
||||
authorize @product
|
||||
@product.destroy
|
||||
ProductService.destroy(@product)
|
||||
head :no_content
|
||||
end
|
||||
|
||||
|
3
app/exceptions/cannot_delete_product_error.rb
Normal file
3
app/exceptions/cannot_delete_product_error.rb
Normal file
@ -0,0 +1,3 @@
|
||||
# Raised when delete a product if this product has used in order
|
||||
class CannotDeleteProductError < StandardError
|
||||
end
|
5
app/exceptions/cart/item_amount_error.rb
Normal file
5
app/exceptions/cart/item_amount_error.rb
Normal file
@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Raised when the item's amount != product's amount
|
||||
class Cart::ItemAmountError < StandardError
|
||||
end
|
5
app/exceptions/cart/quantity_min_error.rb
Normal file
5
app/exceptions/cart/quantity_min_error.rb
Normal file
@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Raised when the item's quantity < product's quantity min
|
||||
class Cart::QuantityMinError < StandardError
|
||||
end
|
@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { react2angular } from 'react2angular';
|
||||
import _ from 'lodash';
|
||||
import { Loader } from '../base/loader';
|
||||
import { IApplication } from '../../models/application';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
@ -154,7 +155,7 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
||||
<span>/ {t('app.public.store_cart.unit')}</span>
|
||||
</div>
|
||||
<select value={item.quantity} onChange={changeProductQuantity(item)}>
|
||||
{Array.from({ length: 100 }, (_, i) => i + item.quantity_min).map(v => (
|
||||
{_.range(item.quantity_min, item.orderable_external_stock + 1, 1).map(v => (
|
||||
<option key={v} value={v}>{v}</option>
|
||||
))}
|
||||
</select>
|
||||
|
@ -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); }
|
||||
})}
|
||||
|
@ -53,16 +53,16 @@ export const OrderActions: React.FC<OrderActionsProps> = ({ order, onSuccess, on
|
||||
let actions = [];
|
||||
switch (order.state) {
|
||||
case 'paid':
|
||||
actions = actions.concat(['in_progress', 'ready', 'canceled', 'refunded']);
|
||||
actions = actions.concat(['in_progress', 'ready', 'delivered', 'canceled', 'refunded']);
|
||||
break;
|
||||
case 'payment_failed':
|
||||
actions = actions.concat(['canceled']);
|
||||
break;
|
||||
case 'in_progress':
|
||||
actions = actions.concat(['ready', 'canceled', 'refunded']);
|
||||
actions = actions.concat(['ready', 'delivered', 'canceled', 'refunded']);
|
||||
break;
|
||||
case 'ready':
|
||||
actions = actions.concat(['canceled', 'refunded']);
|
||||
actions = actions.concat(['delivered', 'canceled', 'refunded']);
|
||||
break;
|
||||
case 'canceled':
|
||||
actions = actions.concat(['refunded']);
|
||||
@ -100,12 +100,14 @@ export const OrderActions: React.FC<OrderActionsProps> = ({ order, onSuccess, on
|
||||
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
options={buildOptions()}
|
||||
onChange={option => handleAction(option)}
|
||||
value={currentAction}
|
||||
styles={customStyles}
|
||||
/>
|
||||
{buildOptions().length > 0 &&
|
||||
<Select
|
||||
options={buildOptions()}
|
||||
onChange={option => handleAction(option)}
|
||||
value={currentAction}
|
||||
styles={customStyles}
|
||||
/>
|
||||
}
|
||||
<FabModal title={t('app.shared.store.order_actions.confirmation_required')}
|
||||
isOpen={modalIsOpen}
|
||||
toggleModal={closeModal}
|
||||
|
@ -34,7 +34,7 @@ export const OrdersDashboard: React.FC<OrdersDashboardProps> = ({ currentUser, o
|
||||
const [totalCount, setTotalCount] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
OrderAPI.index({}).then(res => {
|
||||
OrderAPI.index({ user_id: currentUser.id }).then(res => {
|
||||
setPageCount(res.total_pages);
|
||||
setTotalCount(res.total_count);
|
||||
setOrders(res.data);
|
||||
@ -67,7 +67,7 @@ export const OrdersDashboard: React.FC<OrdersDashboardProps> = ({ currentUser, o
|
||||
*/
|
||||
const handlePagination = (page: number) => {
|
||||
if (page !== currentPage) {
|
||||
OrderAPI.index({ page }).then(res => {
|
||||
OrderAPI.index({ user_id: currentUser.id, page }).then(res => {
|
||||
setCurrentPage(page);
|
||||
setOrders(res.data);
|
||||
setPageCount(res.total_pages);
|
||||
|
@ -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')} />
|
||||
|
@ -49,7 +49,7 @@ export const ProductItem: React.FC<ProductItemProps> = ({ product, onEdit, onDel
|
||||
* Returns CSS class from stock status
|
||||
*/
|
||||
const statusColor = (product: Product) => {
|
||||
if (product.stock.external === 0 && product.stock.internal === 0) {
|
||||
if (product.stock.external < 1 && product.stock.internal < 1) {
|
||||
return 'out-of-stock';
|
||||
}
|
||||
if (product.low_stock_threshold && (product.stock.external < product.low_stock_threshold || product.stock.internal < product.low_stock_threshold)) {
|
||||
@ -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'>
|
||||
|
@ -53,10 +53,10 @@ export const StoreProductItem: React.FC<StoreProductItemProps> = ({ product, car
|
||||
* Returns CSS class from stock status
|
||||
*/
|
||||
const statusColor = (product: Product) => {
|
||||
if (product.stock.external === 0 && product.stock.internal === 0) {
|
||||
if (product.stock.external < (product.quantity_min || 1)) {
|
||||
return 'out-of-stock';
|
||||
}
|
||||
if (product.low_stock_alert) {
|
||||
if (product.low_stock_threshold && product.stock.external < product.low_stock_threshold) {
|
||||
return 'low';
|
||||
}
|
||||
return '';
|
||||
@ -84,16 +84,14 @@ export const StoreProductItem: React.FC<StoreProductItemProps> = ({ product, car
|
||||
{product.quantity_min > 1 &&
|
||||
<span className='min'>{t('app.public.store_product_item.minimum_purchase')}{product.quantity_min}</span>
|
||||
}
|
||||
{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>
|
||||
{product.stock.external > 0 &&
|
||||
{product.stock.external > (product.quantity_min || 1) &&
|
||||
<FabButton icon={<i className="fas fa-cart-arrow-down" />} className="main-action-btn" onClick={addProductToCart}>
|
||||
{t('app.public.store_product_item.add')}
|
||||
</FabButton>
|
||||
|
@ -68,10 +68,10 @@ export const StoreProduct: React.FC<StoreProductProps> = ({ productSlug, current
|
||||
* Returns CSS class from stock status
|
||||
*/
|
||||
const statusColor = (product: Product) => {
|
||||
if (product.stock.external === 0 && product.stock.internal === 0) {
|
||||
if (product.stock.external < (product.quantity_min || 1)) {
|
||||
return 'out-of-stock';
|
||||
}
|
||||
if (product.low_stock_alert) {
|
||||
if (product.low_stock_threshold && product.stock.external < product.low_stock_threshold) {
|
||||
return 'low';
|
||||
}
|
||||
};
|
||||
@ -80,7 +80,7 @@ export const StoreProduct: React.FC<StoreProductProps> = ({ productSlug, current
|
||||
* Return product's stock status
|
||||
*/
|
||||
const productStockStatus = (product: Product) => {
|
||||
if (product.stock.external === 0) {
|
||||
if (product.stock.external < (product.quantity_min || 1)) {
|
||||
return <span>{t('app.public.store_product_item.out_of_stock')}</span>;
|
||||
}
|
||||
if (product.low_stock_threshold && product.stock.external < product.low_stock_threshold) {
|
||||
@ -95,7 +95,9 @@ export const StoreProduct: React.FC<StoreProductProps> = ({ productSlug, current
|
||||
const setCount = (type: 'add' | 'remove') => {
|
||||
switch (type) {
|
||||
case 'add':
|
||||
setToCartCount(toCartCount + 1);
|
||||
if (toCartCount < product.stock.external) {
|
||||
setToCartCount(toCartCount + 1);
|
||||
}
|
||||
break;
|
||||
case 'remove':
|
||||
if (toCartCount > product.quantity_min) {
|
||||
@ -116,10 +118,12 @@ export const StoreProduct: React.FC<StoreProductProps> = ({ productSlug, current
|
||||
* Add product to cart
|
||||
*/
|
||||
const addToCart = () => {
|
||||
CartAPI.addItem(cart, product.id, toCartCount).then(data => {
|
||||
setCart(data);
|
||||
onSuccess(t('app.public.store.add_to_cart_success'));
|
||||
}).catch(onError);
|
||||
if (toCartCount <= product.stock.external) {
|
||||
CartAPI.addItem(cart, product.id, toCartCount).then(data => {
|
||||
setCart(data);
|
||||
onSuccess(t('app.public.store.add_to_cart_success'));
|
||||
}).catch(onError);
|
||||
}
|
||||
};
|
||||
|
||||
if (product) {
|
||||
@ -176,10 +180,10 @@ 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 > 0 &&
|
||||
{product.stock.external > (product.quantity_min || 1) &&
|
||||
<div className='to-cart'>
|
||||
{product.quantity_min > 1 &&
|
||||
<span className='min'>{t('app.public.store_product_item.minimum_purchase')}{product.quantity_min}</span>
|
||||
@ -187,6 +191,8 @@ export const StoreProduct: React.FC<StoreProductProps> = ({ productSlug, current
|
||||
<FabButton onClick={() => setCount('remove')} icon={<Minus size={16} />} className="minus" />
|
||||
<input type="number"
|
||||
value={toCartCount}
|
||||
min={product.quantity_min}
|
||||
max={product.stock.external}
|
||||
onChange={evt => typeCount(evt)} />
|
||||
<FabButton onClick={() => setCount('add')} icon={<Plus size={16} />} className="plus" />
|
||||
<FabButton onClick={() => addToCart()} icon={<i className="fas fa-cart-arrow-down" />}
|
||||
|
@ -32,6 +32,7 @@ export interface Order {
|
||||
orderable_name: string,
|
||||
orderable_ref?: string,
|
||||
orderable_main_image_url?: string,
|
||||
orderable_external_stock: number,
|
||||
quantity: number,
|
||||
quantity_min: number,
|
||||
amount: number,
|
||||
|
@ -71,6 +71,7 @@ class NotificationType
|
||||
notify_admin_user_proof_of_identity_refusal
|
||||
notify_user_order_is_ready
|
||||
notify_user_order_is_canceled
|
||||
notify_user_order_is_refunded
|
||||
]
|
||||
# deprecated:
|
||||
# - notify_member_subscribed_plan_is_changed
|
||||
|
@ -10,7 +10,7 @@ class Order < PaymentDocument
|
||||
has_one :payment_gateway_object, as: :item
|
||||
has_many :order_activities, dependent: :destroy
|
||||
|
||||
ALL_STATES = %w[cart paid payment_failed refunded in_progress ready canceled return].freeze
|
||||
ALL_STATES = %w[cart paid payment_failed refunded in_progress ready canceled delivered].freeze
|
||||
enum state: ALL_STATES.zip(ALL_STATES).to_h
|
||||
|
||||
validates :token, :state, presence: true
|
||||
|
@ -4,7 +4,7 @@
|
||||
class OrderActivity < ApplicationRecord
|
||||
belongs_to :order
|
||||
|
||||
TYPES = %w[paid payment_failed refunded in_progress ready canceled return note].freeze
|
||||
TYPES = %w[paid payment_failed refunded in_progress ready canceled delivered note].freeze
|
||||
enum activity_type: TYPES.zip(TYPES).to_h
|
||||
|
||||
validates :activity_type, presence: true
|
||||
|
@ -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) }
|
||||
|
||||
|
@ -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
|
||||
|
@ -43,12 +43,8 @@ class PDF::Invoice < Prawn::Document
|
||||
end
|
||||
text I18n.t('invoices.code', CODE: Setting.get('invoice_code-value')), leading: 3 if Setting.get('invoice_code-active')
|
||||
if invoice.main_item.object_type != WalletTransaction.name
|
||||
if invoice.is_a?(Avoir)
|
||||
text I18n.t('invoices.order_number', NUMBER: invoice.invoice.order_number), leading: 3
|
||||
else
|
||||
order_number = invoice.main_item.object_type == OrderItem.name ? invoice.main_item.object.order.reference : invoice.order_number
|
||||
text I18n.t('invoices.order_number', NUMBER: order_number), leading: 3
|
||||
end
|
||||
order_number = invoice.main_item.object_type == OrderItem.name ? invoice.main_item.object.order.reference : invoice.order_number
|
||||
text I18n.t('invoices.order_number', NUMBER: order_number), leading: 3
|
||||
end
|
||||
if invoice.is_a?(Avoir)
|
||||
text I18n.t('invoices.refund_invoice_issued_on_DATE', DATE: I18n.l(invoice.avoir_date.to_date))
|
||||
|
@ -10,13 +10,13 @@ class Cart::AddItemService
|
||||
item = order.order_items.find_by(orderable: orderable)
|
||||
quantity = orderable.quantity_min > quantity.to_i && item.nil? ? orderable.quantity_min : quantity.to_i
|
||||
|
||||
raise Cart::OutStockError if quantity > orderable.stock['external']
|
||||
|
||||
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
|
||||
raise Cart::OutStockError if item.quantity > orderable.stock['external']
|
||||
|
||||
order.total += (item.amount * quantity.to_i)
|
||||
ActiveRecord::Base.transaction do
|
||||
item.save
|
||||
|
@ -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)
|
||||
|
||||
|
@ -1,16 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Provides methods for cancel an order
|
||||
class Orders::CancelOrderService
|
||||
class Orders::OrderCanceledService
|
||||
def call(order, current_user)
|
||||
raise ::UpdateOrderStateError if %w[cart payment_failed canceled refunded].include?(order.state)
|
||||
raise ::UpdateOrderStateError if %w[cart canceled refunded delivered].include?(order.state)
|
||||
|
||||
order.state = 'canceled'
|
||||
ActiveRecord::Base.transaction do
|
||||
activity = order.order_activities.create(activity_type: 'canceled', operator_profile_id: current_user.invoicing_profile.id)
|
||||
order.order_items.each do |item|
|
||||
ProductService.update_stock(item.orderable, 'external', 'cancelled', item.quantity, item.id)
|
||||
end
|
||||
order.save
|
||||
NotificationCenter.call type: 'notify_user_order_is_canceled',
|
||||
receiver: order.statistic_profile.user,
|
13
app/services/orders/order_delivered_service.rb
Normal file
13
app/services/orders/order_delivered_service.rb
Normal file
@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Provides methods for set order to delivered state
|
||||
class Orders::OrderDeliveredService
|
||||
def call(order, current_user)
|
||||
raise ::UpdateOrderStateError if %w[cart payment_failed canceled refunded delivered].include?(order.state)
|
||||
|
||||
order.state = 'delivered'
|
||||
order.order_activities.push(OrderActivity.new(activity_type: 'delivered', operator_profile_id: current_user.invoicing_profile.id))
|
||||
order.save
|
||||
order.reload
|
||||
end
|
||||
end
|
@ -3,7 +3,7 @@
|
||||
# Provides methods for set order to ready state
|
||||
class Orders::OrderReadyService
|
||||
def call(order, current_user, note = '')
|
||||
raise ::UpdateOrderStateError if %w[cart payment_failed ready canceled refunded].include?(order.state)
|
||||
raise ::UpdateOrderStateError if %w[cart payment_failed ready canceled refunded delivered].include?(order.state)
|
||||
|
||||
order.state = 'ready'
|
||||
ActiveRecord::Base.transaction do
|
||||
|
18
app/services/orders/order_refunded_service.rb
Normal file
18
app/services/orders/order_refunded_service.rb
Normal file
@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Provides methods for refund an order
|
||||
class Orders::OrderRefundedService
|
||||
def call(order, current_user)
|
||||
raise ::UpdateOrderStateError if %w[cart payment_error refunded delivered].include?(order.state)
|
||||
|
||||
order.state = 'refunded'
|
||||
ActiveRecord::Base.transaction do
|
||||
activity = order.order_activities.create(activity_type: 'refunded', operator_profile_id: current_user.invoicing_profile.id)
|
||||
order.save
|
||||
NotificationCenter.call type: 'notify_user_order_is_refunded',
|
||||
receiver: order.statistic_profile.user,
|
||||
attached_object: activity
|
||||
end
|
||||
order.reload
|
||||
end
|
||||
end
|
@ -46,7 +46,9 @@ class Orders::OrderService
|
||||
def self.update_state(order, current_user, state, note = nil)
|
||||
return ::Orders::SetInProgressService.new.call(order, current_user) if state == 'in_progress'
|
||||
return ::Orders::OrderReadyService.new.call(order, current_user, note) if state == 'ready'
|
||||
return ::Orders::CancelOrderService.new.call(order, current_user) if state == 'canceled'
|
||||
return ::Orders::OrderCanceledService.new.call(order, current_user) if state == 'canceled'
|
||||
return ::Orders::OrderDeliveredService.new.call(order, current_user) if state == 'delivered'
|
||||
return ::Orders::OrderRefundedService.new.call(order, current_user) if state == 'refunded'
|
||||
end
|
||||
|
||||
def in_stock?(order, stock_type = 'external')
|
||||
@ -56,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
|
||||
|
@ -3,7 +3,7 @@
|
||||
# Provides methods for set in progress state to order
|
||||
class Orders::SetInProgressService
|
||||
def call(order, current_user)
|
||||
raise ::UpdateOrderStateError if %w[cart payment_failed in_progress canceled refunded].include?(order.state)
|
||||
raise ::UpdateOrderStateError if %w[cart payment_failed in_progress canceled refunded delivered].include?(order.state)
|
||||
|
||||
order.state = 'in_progress'
|
||||
order.order_activities.push(OrderActivity.new(activity_type: 'in_progress', operator_profile_id: current_user.invoicing_profile.id))
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,2 @@
|
||||
json.title notification.notification_type
|
||||
json.description t('.order_refunded', REFERENCE: notification.attached_object.order.reference)
|
@ -27,6 +27,7 @@ json.order_items_attributes order.order_items.order(created_at: :asc) do |item|
|
||||
json.orderable_name item.orderable.name
|
||||
json.orderable_ref item.orderable.sku
|
||||
json.orderable_main_image_url item.orderable.main_image&.attachment_url
|
||||
json.orderable_external_stock item.orderable.stock['external']
|
||||
json.quantity item.quantity
|
||||
json.quantity_min item.orderable.quantity_min
|
||||
json.amount item.amount / 100.0
|
||||
|
@ -0,0 +1,5 @@
|
||||
<%= render 'notifications_mailer/shared/hello', recipient: @recipient %>
|
||||
|
||||
<p>
|
||||
<%= t('.body.notify_user_order_is_refunded', REFERENCE: @attached_object.order.reference) %>
|
||||
</p>
|
@ -2054,12 +2054,13 @@ en:
|
||||
filter_period_to: "to"
|
||||
state:
|
||||
cart: 'Cart'
|
||||
in_progress: 'In progress'
|
||||
in_progress: 'Under preparation'
|
||||
paid: "Paid"
|
||||
payment_failed: "Payment error"
|
||||
canceled: "Canceled"
|
||||
ready: "Ready"
|
||||
refunded: "Refunded"
|
||||
delivered: "Delivered"
|
||||
sort:
|
||||
newest: "Newest first"
|
||||
oldest: "Oldest first"
|
||||
|
@ -568,12 +568,13 @@ en:
|
||||
last_update: "Last update"
|
||||
state:
|
||||
cart: 'Cart'
|
||||
in_progress: 'In progress'
|
||||
in_progress: 'Under preparation'
|
||||
paid: "Paid"
|
||||
payment_failed: "Payment error"
|
||||
canceled: "Canceled"
|
||||
ready: "Ready"
|
||||
refunded: "Refunded"
|
||||
delivered: "Delivered"
|
||||
show_order:
|
||||
back_to_list: "Back to list"
|
||||
see_invoice: "See invoice"
|
||||
@ -593,12 +594,13 @@ en:
|
||||
cart_total: "Cart total"
|
||||
state:
|
||||
cart: 'Cart'
|
||||
in_progress: 'In progress'
|
||||
in_progress: 'Under preparation'
|
||||
paid: "Paid"
|
||||
payment_failed: "Payment error"
|
||||
canceled: "Canceled"
|
||||
ready: "Ready"
|
||||
refunded: "Refunded"
|
||||
delivered: "Delivered"
|
||||
payment:
|
||||
by_wallet: "by wallet"
|
||||
settlement_by_debit_card: "Settlement by debit card"
|
||||
@ -610,21 +612,26 @@ en:
|
||||
order_actions:
|
||||
state:
|
||||
cart: 'Cart'
|
||||
in_progress: 'In progress'
|
||||
in_progress: 'Under preparation'
|
||||
paid: "Paid"
|
||||
payment_failed: "Payment error"
|
||||
canceled: "Canceled"
|
||||
ready: "Ready"
|
||||
refunded: "Refunded"
|
||||
delivered: "Delivered"
|
||||
confirm: 'Confirm'
|
||||
confirmation_required: "Confirmation required"
|
||||
confirm_order_in_progress: "This order is in the process of being prepared ?"
|
||||
order_in_progress_success: "Order is under preparation"
|
||||
confirm_order_ready: "This order is ready ?"
|
||||
order_ready_note: ''
|
||||
order_ready_note: 'Leave your message'
|
||||
order_ready_success: "Order is ready"
|
||||
confirm_order_canceled: "Do you want to cancel this order ?"
|
||||
confirm_order_delivered: "This order is delivered ?"
|
||||
order_delivered_success: "Order is delivered"
|
||||
confirm_order_canceled: "Do you want to cancel this order ? You can modify product stock in stock manage."
|
||||
order_canceled_success: "Order is canceled"
|
||||
confirm_order_refunded: "Do you want to refund this order ? You can modify product stock in stock manage."
|
||||
order_refunded_success: "Order is refunded"
|
||||
unsaved_form_alert:
|
||||
modal_title: "You have some unsaved changes"
|
||||
confirmation_message: "If you leave this page, your changes will be lost. Are you sure you want to continue?"
|
||||
|
@ -411,6 +411,8 @@ en:
|
||||
order_ready: "Your command %{REFERENCE} is ready"
|
||||
notify_user_order_is_canceled:
|
||||
order_canceled: "Your command %{REFERENCE} is canceled"
|
||||
notify_user_order_is_refunded:
|
||||
order_refunded: "Your command %{REFERENCE} is refunded"
|
||||
#statistics tools for admins
|
||||
statistics:
|
||||
subscriptions: "Subscriptions"
|
||||
|
@ -381,4 +381,8 @@ en:
|
||||
notify_user_order_is_canceled:
|
||||
subject: "Your command is canceled"
|
||||
body:
|
||||
notify_user_order_is_canceled: "Your command %{REFERENCE} is canceled:"
|
||||
notify_user_order_is_canceled: "Your command %{REFERENCE} is canceled."
|
||||
notify_user_order_is_refunded:
|
||||
subject: "Your command is refunded"
|
||||
body:
|
||||
notify_user_order_is_refunded: "Your command %{REFERENCE} is refunded:"
|
||||
|
Loading…
x
Reference in New Issue
Block a user