diff --git a/app/controllers/api/products_controller.rb b/app/controllers/api/products_controller.rb index 97f648fa3..a77d06e67 100644 --- a/app/controllers/api/products_controller.rb +++ b/app/controllers/api/products_controller.rb @@ -40,7 +40,7 @@ class API::ProductsController < API::ApiController def destroy authorize @product - @product.destroy + ProductService.destroy(@product) head :no_content end diff --git a/app/exceptions/cannot_delete_product_error.rb b/app/exceptions/cannot_delete_product_error.rb new file mode 100644 index 000000000..f4e2eec80 --- /dev/null +++ b/app/exceptions/cannot_delete_product_error.rb @@ -0,0 +1,3 @@ +# Raised when delete a product if this product has used in order +class CannotDeleteProductError < StandardError +end diff --git a/app/exceptions/cart/item_amount_error.rb b/app/exceptions/cart/item_amount_error.rb new file mode 100644 index 000000000..9085d68ce --- /dev/null +++ b/app/exceptions/cart/item_amount_error.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Raised when the item's amount != product's amount +class Cart::ItemAmountError < StandardError +end diff --git a/app/exceptions/cart/quantity_min_error.rb b/app/exceptions/cart/quantity_min_error.rb new file mode 100644 index 000000000..f34e4c1b2 --- /dev/null +++ b/app/exceptions/cart/quantity_min_error.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Raised when the item's quantity < product's quantity min +class Cart::QuantityMinError < StandardError +end diff --git a/app/frontend/src/javascript/components/cart/store-cart.tsx b/app/frontend/src/javascript/components/cart/store-cart.tsx index 9aef76649..7b1881190 100644 --- a/app/frontend/src/javascript/components/cart/store-cart.tsx +++ b/app/frontend/src/javascript/components/cart/store-cart.tsx @@ -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 = ({ onSuccess, onError, currentUser, / {t('app.public.store_cart.unit')} diff --git a/app/frontend/src/javascript/components/form/form-input.tsx b/app/frontend/src/javascript/components/form/form-input.tsx index 31b04bec1..78f15d801 100644 --- a/app/frontend/src/javascript/components/form/form-input.tsx +++ b/app/frontend/src/javascript/components/form/form-input.tsx @@ -59,7 +59,7 @@ export const FormInput = ({ id, re {...register(id as FieldPath, { ...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>, onChange: (e) => { handleChange(e); } })} diff --git a/app/frontend/src/javascript/components/store/order-actions.tsx b/app/frontend/src/javascript/components/store/order-actions.tsx index 4cab008e9..0afdeea9a 100644 --- a/app/frontend/src/javascript/components/store/order-actions.tsx +++ b/app/frontend/src/javascript/components/store/order-actions.tsx @@ -53,16 +53,16 @@ export const OrderActions: React.FC = ({ 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 = ({ order, onSuccess, on return ( <> - handleAction(option)} + value={currentAction} + styles={customStyles} + /> + } = ({ currentUser, o const [totalCount, setTotalCount] = useState(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 = ({ 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); diff --git a/app/frontend/src/javascript/components/store/product-form.tsx b/app/frontend/src/javascript/components/store/product-form.tsx index 6bb1a36d6..07296e1be 100644 --- a/app/frontend/src/javascript/components/store/product-form.tsx +++ b/app/frontend/src/javascript/components/store/product-form.tsx @@ -280,7 +280,7 @@ export const ProductForm: React.FC = ({ product, title, onSucc diff --git a/app/frontend/src/javascript/components/store/product-item.tsx b/app/frontend/src/javascript/components/store/product-item.tsx index 9c85219ab..c37f97dc1 100644 --- a/app/frontend/src/javascript/components/store/product-item.tsx +++ b/app/frontend/src/javascript/components/store/product-item.tsx @@ -49,7 +49,7 @@ export const ProductItem: React.FC = ({ 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 = ({ product, onEdit, onDel {t('app.admin.store.product_item.stock.external')}

{product.stock.external}

- {product.amount && -
-

{FormatLib.price(product.amount)}

- / {t('app.admin.store.product_item.unit')} -
- } +
+

{FormatLib.price(product.amount || 0)}

+ / {t('app.admin.store.product_item.unit')} +
diff --git a/app/frontend/src/javascript/components/store/store-product-item.tsx b/app/frontend/src/javascript/components/store/store-product-item.tsx index 62e50378e..9c8934607 100644 --- a/app/frontend/src/javascript/components/store/store-product-item.tsx +++ b/app/frontend/src/javascript/components/store/store-product-item.tsx @@ -53,10 +53,10 @@ export const StoreProductItem: React.FC = ({ 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 = ({ product, car {product.quantity_min > 1 && {t('app.public.store_product_item.minimum_purchase')}{product.quantity_min} } - {product.amount && -
-

{FormatLib.price(product.amount)}

- / {t('app.public.store_product_item.unit')} -
- } +
+

{FormatLib.price(product.amount || 0)}

+ / {t('app.public.store_product_item.unit')} +
{productStockStatus(product)} - {product.stock.external > 0 && + {product.stock.external > (product.quantity_min || 1) && } className="main-action-btn" onClick={addProductToCart}> {t('app.public.store_product_item.add')} diff --git a/app/frontend/src/javascript/components/store/store-product.tsx b/app/frontend/src/javascript/components/store/store-product.tsx index 407f58567..21c39d0bc 100644 --- a/app/frontend/src/javascript/components/store/store-product.tsx +++ b/app/frontend/src/javascript/components/store/store-product.tsx @@ -68,10 +68,10 @@ export const StoreProduct: React.FC = ({ 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 = ({ 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 {t('app.public.store_product_item.out_of_stock')}; } if (product.low_stock_threshold && product.stock.external < product.low_stock_threshold) { @@ -95,7 +95,9 @@ export const StoreProduct: React.FC = ({ 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 = ({ 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 = ({ productSlug, current {productStockStatus(product)}
-

{FormatLib.price(product.amount)} TTC

+

{FormatLib.price(product.amount || 0)} TTC

/ {t('app.public.store_product_item.unit')}
- {product.stock.external > 0 && + {product.stock.external > (product.quantity_min || 1) &&
{product.quantity_min > 1 && {t('app.public.store_product_item.minimum_purchase')}{product.quantity_min} @@ -187,6 +191,8 @@ export const StoreProduct: React.FC = ({ productSlug, current setCount('remove')} icon={} className="minus" /> typeCount(evt)} /> setCount('add')} icon={} className="plus" /> addToCart()} icon={} diff --git a/app/frontend/src/javascript/models/order.ts b/app/frontend/src/javascript/models/order.ts index 5b0380421..cf986e86a 100644 --- a/app/frontend/src/javascript/models/order.ts +++ b/app/frontend/src/javascript/models/order.ts @@ -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, diff --git a/app/models/notification_type.rb b/app/models/notification_type.rb index 4d0fec54a..ed14b37be 100644 --- a/app/models/notification_type.rb +++ b/app/models/notification_type.rb @@ -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 diff --git a/app/models/order.rb b/app/models/order.rb index 96d7c249d..0586aa81d 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -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 diff --git a/app/models/order_activity.rb b/app/models/order_activity.rb index b8703d867..1e2fcd513 100644 --- a/app/models/order_activity.rb +++ b/app/models/order_activity.rb @@ -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 diff --git a/app/models/product.rb b/app/models/product.rb index 682a3f8fe..37f5d1a04 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -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) } diff --git a/app/models/product_category.rb b/app/models/product_category.rb index 40af30a1a..afe4595ae 100644 --- a/app/models/product_category.rb +++ b/app/models/product_category.rb @@ -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 diff --git a/app/pdfs/pdf/invoice.rb b/app/pdfs/pdf/invoice.rb index 4b2824f43..8a1f88371 100644 --- a/app/pdfs/pdf/invoice.rb +++ b/app/pdfs/pdf/invoice.rb @@ -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)) diff --git a/app/services/cart/add_item_service.rb b/app/services/cart/add_item_service.rb index 8f8dfa013..4fbc4ea3a 100644 --- a/app/services/cart/add_item_service.rb +++ b/app/services/cart/add_item_service.rb @@ -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 diff --git a/app/services/checkout/payment_service.rb b/app/services/checkout/payment_service.rb index 470086f9b..429870e5f 100644 --- a/app/services/checkout/payment_service.rb +++ b/app/services/checkout/payment_service.rb @@ -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) diff --git a/app/services/orders/cancel_order_service.rb b/app/services/orders/order_canceled_service.rb similarity index 65% rename from app/services/orders/cancel_order_service.rb rename to app/services/orders/order_canceled_service.rb index 8054db167..b9a99b1ed 100644 --- a/app/services/orders/cancel_order_service.rb +++ b/app/services/orders/order_canceled_service.rb @@ -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, diff --git a/app/services/orders/order_delivered_service.rb b/app/services/orders/order_delivered_service.rb new file mode 100644 index 000000000..56c0a0bb7 --- /dev/null +++ b/app/services/orders/order_delivered_service.rb @@ -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 diff --git a/app/services/orders/order_ready_service.rb b/app/services/orders/order_ready_service.rb index 330bf83bb..a95a40088 100644 --- a/app/services/orders/order_ready_service.rb +++ b/app/services/orders/order_ready_service.rb @@ -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 diff --git a/app/services/orders/order_refunded_service.rb b/app/services/orders/order_refunded_service.rb new file mode 100644 index 000000000..0935ee1c0 --- /dev/null +++ b/app/services/orders/order_refunded_service.rb @@ -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 diff --git a/app/services/orders/order_service.rb b/app/services/orders/order_service.rb index 35663cd74..4b1209131 100644 --- a/app/services/orders/order_service.rb +++ b/app/services/orders/order_service.rb @@ -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 diff --git a/app/services/orders/refund_order_service.rb b/app/services/orders/refund_order_service.rb deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/services/orders/set_in_progress_service.rb b/app/services/orders/set_in_progress_service.rb index 91f32d068..9aa6c8fb4 100644 --- a/app/services/orders/set_in_progress_service.rb +++ b/app/services/orders/set_in_progress_service.rb @@ -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)) diff --git a/app/services/product_service.rb b/app/services/product_service.rb index 38db4eefb..ff7f8432e 100644 --- a/app/services/product_service.rb +++ b/app/services/product_service.rb @@ -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 diff --git a/app/views/api/notifications/_notify_user_order_is_refunded.json.jbuilder b/app/views/api/notifications/_notify_user_order_is_refunded.json.jbuilder new file mode 100644 index 000000000..832b8604b --- /dev/null +++ b/app/views/api/notifications/_notify_user_order_is_refunded.json.jbuilder @@ -0,0 +1,2 @@ +json.title notification.notification_type +json.description t('.order_refunded', REFERENCE: notification.attached_object.order.reference) diff --git a/app/views/api/orders/_order.json.jbuilder b/app/views/api/orders/_order.json.jbuilder index 7bc6ca3e0..7c4836fae 100644 --- a/app/views/api/orders/_order.json.jbuilder +++ b/app/views/api/orders/_order.json.jbuilder @@ -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 diff --git a/app/views/notifications_mailer/notify_user_order_is_refunded.erb b/app/views/notifications_mailer/notify_user_order_is_refunded.erb new file mode 100644 index 000000000..1f4f74271 --- /dev/null +++ b/app/views/notifications_mailer/notify_user_order_is_refunded.erb @@ -0,0 +1,5 @@ +<%= render 'notifications_mailer/shared/hello', recipient: @recipient %> + +

+ <%= t('.body.notify_user_order_is_refunded', REFERENCE: @attached_object.order.reference) %> +

diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index a31c4f414..191a8fc87 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -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" diff --git a/config/locales/app.shared.en.yml b/config/locales/app.shared.en.yml index 859d12af1..bfa293431 100644 --- a/config/locales/app.shared.en.yml +++ b/config/locales/app.shared.en.yml @@ -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?" diff --git a/config/locales/en.yml b/config/locales/en.yml index 0eb43b7f7..432dbe0af 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -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" diff --git a/config/locales/mails.en.yml b/config/locales/mails.en.yml index 8040a7ef8..c438ef62d 100644 --- a/config/locales/mails.en.yml +++ b/config/locales/mails.en.yml @@ -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:"