mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
(feat) notify_admin_order_is_paid
This commit is contained in:
parent
f92a4a741b
commit
ed072184bf
@ -3,7 +3,13 @@
|
||||
- Report user's prepaid packs in the dashboard
|
||||
- Export external ID and private notes in the members excel export
|
||||
- Ability to buy a new prepaid pack from the user's dashboard
|
||||
- Improved public calendar loading time
|
||||
- Improved calendars loading time
|
||||
- Admin notification when an order was placed
|
||||
- Management of notifications preferences for admins
|
||||
- Display custom banners in machines/trainings/events lists
|
||||
- Filter projects by status
|
||||
- Maximum validity period for trainings authorizations
|
||||
- Automatically cancel trainings with insufficient attendees
|
||||
- Fix a bug: event image updates are not reflected unless the browser's cache is purged
|
||||
- Fix a bug: schedules jobs are not launched at the right time
|
||||
- Fix a bug: unable to update the title of a training
|
||||
|
@ -14,7 +14,7 @@ import { MemberSelect } from '../user/member-select';
|
||||
import { User } from '../../models/user';
|
||||
import { FormInput } from '../form/form-input';
|
||||
import OrderAPI from '../../api/order';
|
||||
import { Order, OrderIndexFilter, OrderSortOption } from '../../models/order';
|
||||
import { Order, OrderIndexFilter, OrderSortOption, OrderState } from '../../models/order';
|
||||
import { FabPagination } from '../base/fab-pagination';
|
||||
import { CaretDoubleUp, X } from 'phosphor-react';
|
||||
import { ChecklistOption, SelectOption } from '../../models/select';
|
||||
@ -50,7 +50,7 @@ const Orders: React.FC<OrdersProps> = ({ currentUser, onError }) => {
|
||||
const [pageCount, setPageCount] = useState<number>(0);
|
||||
const [totalCount, setTotalCount] = useState<number>(0);
|
||||
const [reference, setReference] = useState<string>(filters.reference);
|
||||
const [states, setStates] = useState<Array<string>>(filters.states);
|
||||
const [states, setStates] = useState<Array<OrderState>>(filters.states);
|
||||
const [user, setUser] = useState<{ id: number, name?: string }>(filters.user);
|
||||
const [periodFrom, setPeriodFrom] = useState<string>(filters.period_from);
|
||||
const [periodTo, setPeriodTo] = useState<string>(filters.period_to);
|
||||
@ -64,7 +64,7 @@ const Orders: React.FC<OrdersProps> = ({ currentUser, onError }) => {
|
||||
}).catch(onError);
|
||||
}, [filters]);
|
||||
|
||||
const statusOptions: ChecklistOption<string>[] = [
|
||||
const statusOptions: ChecklistOption<OrderState>[] = [
|
||||
{ value: 'cart', label: t('app.admin.store.orders.state.cart') },
|
||||
{ value: 'paid', label: t('app.admin.store.orders.state.paid') },
|
||||
{ value: 'payment_failed', label: t('app.admin.store.orders.state.payment_failed') },
|
||||
@ -108,7 +108,7 @@ const Orders: React.FC<OrdersProps> = ({ currentUser, onError }) => {
|
||||
/**
|
||||
* Clear filter by type
|
||||
*/
|
||||
const removeFilter = (filterType: string, state?: string) => {
|
||||
const removeFilter = (filterType: string, state?: OrderState) => {
|
||||
return () => {
|
||||
setFilters(draft => {
|
||||
draft.page = 1;
|
||||
@ -185,7 +185,7 @@ const Orders: React.FC<OrdersProps> = ({ currentUser, onError }) => {
|
||||
/**
|
||||
* Filter: by status
|
||||
*/
|
||||
const handleSelectStatus = (s: ChecklistOption<string>, checked: boolean) => {
|
||||
const handleSelectStatus = (s: ChecklistOption<OrderState>, checked: boolean) => {
|
||||
const list = [...states];
|
||||
checked
|
||||
? list.push(s.value)
|
||||
|
@ -79,7 +79,8 @@ export const notificationTypeNames = [
|
||||
'notify_user_order_is_canceled',
|
||||
'notify_user_order_is_refunded',
|
||||
'notify_admin_low_stock_threshold',
|
||||
'notify_admin_training_auto_cancelled'
|
||||
'notify_admin_training_auto_cancelled',
|
||||
'notify_admin_order_is_paid'
|
||||
] as const;
|
||||
|
||||
export type NotificationTypeName = typeof notificationTypeNames[number];
|
||||
|
@ -8,6 +8,8 @@ import type { CartItemReservationType, CartItemType } from './cart_item';
|
||||
|
||||
export type OrderableType = 'Product' | CartItemType;
|
||||
|
||||
export type OrderState = 'cart'|'paid'|'payment_failed'|'refunded'|'in_progress'|'ready'|'canceled'|'delivered';
|
||||
|
||||
export interface OrderItem {
|
||||
id: number,
|
||||
orderable_type: OrderableType,
|
||||
@ -51,7 +53,7 @@ export interface Order {
|
||||
},
|
||||
operator_profile_id?: number,
|
||||
reference?: string,
|
||||
state?: string,
|
||||
state?: OrderState,
|
||||
total?: number,
|
||||
coupon?: Coupon,
|
||||
created_at?: TDateISO,
|
||||
@ -82,7 +84,7 @@ export interface OrderIndexFilter extends ApiFilter {
|
||||
},
|
||||
page?: number,
|
||||
sort?: OrderSortOption
|
||||
states?: Array<string>,
|
||||
states?: Array<OrderState>,
|
||||
period_from?: string,
|
||||
period_to?: string
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# NotificationType defines the different types of Notification.
|
||||
# When recording a new notification type in db, you might also want to add it in:
|
||||
# test/fixtures/notification_types.yml
|
||||
# app/frontend/src/javascript/models/notification-type.ts
|
||||
# config/locales/app.logged.en.yml
|
||||
# To add a new notification type in db, you must add it in:
|
||||
# - db/seeds/notification_types.rb
|
||||
# - app/views/api/notifications/_XXXXXX.json.jbuilder
|
||||
# - app/views/notifications_mailer/XXXXXX.html.erb
|
||||
# - app/frontend/src/javascript/models/notification-type.ts
|
||||
# - config/locales/app.logged.en.yml
|
||||
# - test/fixtures/notification_types.yml
|
||||
# If you change the name of a category, or create a new one, please add it in:
|
||||
# app/frontend/src/javascript/models/notification-preference.ts
|
||||
# - app/frontend/src/javascript/models/notification-type.ts
|
||||
class NotificationType < ApplicationRecord
|
||||
has_many :notifications, dependent: :destroy
|
||||
has_many :notification_preferences, dependent: :destroy
|
||||
|
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Provides methods for cancel an order
|
||||
# Provides a method to cancel an order
|
||||
class Orders::OrderCanceledService
|
||||
def call(order, current_user)
|
||||
raise ::UpdateOrderStateError if %w[cart canceled refunded delivered].include?(order.state)
|
||||
|
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Provides methods for set order to delivered state
|
||||
# Provides a method to set the order state to delivered
|
||||
class Orders::OrderDeliveredService
|
||||
def call(order, current_user)
|
||||
raise ::UpdateOrderStateError if %w[cart payment_failed canceled refunded delivered].include?(order.state)
|
||||
|
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Provides methods for set order to ready state
|
||||
# Provides a method to set the order state to ready
|
||||
class Orders::OrderReadyService
|
||||
def call(order, current_user, note = '')
|
||||
raise ::UpdateOrderStateError if %w[cart payment_failed ready canceled refunded delivered].include?(order.state)
|
||||
|
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Provides methods for refund an order
|
||||
# Provides a method to refund an order
|
||||
class Orders::OrderRefundedService
|
||||
def call(order, current_user)
|
||||
raise ::UpdateOrderStateError if %w[cart payment_error refunded delivered].include?(order.state)
|
||||
|
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Provides methods for set in progress state to order
|
||||
# Provides a method to set the order state to "in progress"
|
||||
class Orders::SetInProgressService
|
||||
def call(order, current_user)
|
||||
raise ::UpdateOrderStateError if %w[cart payment_failed in_progress canceled refunded delivered].include?(order.state)
|
||||
|
@ -28,16 +28,22 @@ module Payments::PaymentConcern
|
||||
payment_method
|
||||
end
|
||||
order.state = 'paid'
|
||||
# we change the order creation date to the date of payment to be more consistent for admins when filtering on orders
|
||||
order.created_at = Time.current
|
||||
if payment_id && payment_type
|
||||
order.payment_gateway_object = PaymentGatewayObject.new(gateway_object_id: payment_id, gateway_object_type: payment_type)
|
||||
end
|
||||
order.order_activities.create(activity_type: 'paid', operator_profile_id: order.operator_profile_id)
|
||||
activity = order.order_activities.create(activity_type: 'paid', operator_profile_id: order.operator_profile_id)
|
||||
order.order_items.each do |item|
|
||||
ProductService.update_stock(item.orderable,
|
||||
[{ stock_type: 'external', reason: 'sold', quantity: item.quantity, order_item_id: item.id }]).save
|
||||
end
|
||||
create_invoice(order, coupon, payment_id, payment_type) if order.save
|
||||
if order.save
|
||||
create_invoice(order, coupon, payment_id, payment_type)
|
||||
NotificationCenter.call type: 'notify_admin_order_is_paid',
|
||||
receiver: User.admins_and_managers,
|
||||
attached_object: activity
|
||||
end
|
||||
order.reload
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,4 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
json.title notification.notification_type
|
||||
json.description t('.order_paid_html', { ID: notification.attached_object.order_id })
|
@ -1 +1,3 @@
|
||||
<%= render 'notifications_mailer/shared/hello', recipient: @recipient %>
|
||||
|
||||
<p><%= t('.body.objects_sync') %></p>
|
||||
|
@ -0,0 +1,6 @@
|
||||
<%= render 'notifications_mailer/shared/hello', recipient: @recipient %>
|
||||
|
||||
<p>
|
||||
<%= t('.body.order_placed', REFERENCE: @attached_object.order.reference, USER: @attached_object.order.statistic_profile.user.profile.full_name) %>
|
||||
<%= link_to t('.body.view_details'), "#{root_url}#!/admin/store/orders/#{@attached_object.order_id}" %>
|
||||
</p>
|
@ -319,3 +319,4 @@ en:
|
||||
notify_admin_payment_schedule_gateway_canceled: "A payment schedule has been canceled by the payment gateway"
|
||||
notify_project_collaborator_to_valid: "You are invited to collaborate on a project"
|
||||
notify_project_author_when_collaborator_valid: "A collaborator has accepted your invitation to join your project"
|
||||
notify_admin_order_is_paid: "A new order has been placed"
|
||||
|
@ -412,6 +412,8 @@ en:
|
||||
user_NAME_changed_ROLE_html: "User <strong><em>%{NAME}</strong></em> is now %{ROLE}."
|
||||
notify_admin_objects_stripe_sync:
|
||||
all_objects_sync: "All data were successfully synchronized on Stripe."
|
||||
notify_admin_order_is_paid:
|
||||
order_paid_html: "A new order has been placed. <a href='/#!/admin/store/orders/%{ID}'>View details</a>."
|
||||
notify_user_when_payment_schedule_ready:
|
||||
your_schedule_is_ready_html: "Your payment schedule #%{REFERENCE}, of %{AMOUNT}, is ready. <a href='api/payment_schedules/%{SCHEDULE_ID}/download' target='_blank'>Click here to download</a>."
|
||||
notify_admin_payment_schedule_error:
|
||||
|
@ -317,6 +317,11 @@ en:
|
||||
subject: "Stripe synchronization"
|
||||
body:
|
||||
objects_sync: "All members, coupons, machines, trainings, spaces and plans were successfully synchronized on Stripe."
|
||||
notify_admin_order_is_paid:
|
||||
subject: "New order"
|
||||
body:
|
||||
order_placed: "A new order (%{REFERENCE}) has been placed and paid by %{USER}."
|
||||
view_details: ""
|
||||
notify_member_payment_schedule_ready:
|
||||
subject: "Your payment schedule"
|
||||
body:
|
||||
|
@ -15,3 +15,11 @@ unless NotificationType.find_by(name: 'notify_member_training_invalidated')
|
||||
is_configurable: false
|
||||
)
|
||||
end
|
||||
|
||||
unless NotificationType.find_by(name: 'notify_admin_order_is_paid')
|
||||
NotificationType.create!(
|
||||
name: 'notify_admin_order_is_paid',
|
||||
category: 'shop',
|
||||
is_configurable: true
|
||||
)
|
||||
end
|
||||
|
8
test/fixtures/notification_types.yml
vendored
8
test/fixtures/notification_types.yml
vendored
@ -541,3 +541,11 @@ notification_type_68:
|
||||
is_configurable: true
|
||||
created_at: 2023-02-02 08:25:33.443888000 Z
|
||||
updated_at: 2023-02-02 08:25:33.443888000 Z
|
||||
|
||||
notification_type_69:
|
||||
id: 69
|
||||
name: notify_admin_order_is_paid
|
||||
category: shop
|
||||
is_configurable: true
|
||||
created_at: 2023-02-16 10:42:39.143888000 Z
|
||||
updated_at: 2023-02-16 10:42:39.143888000 Z
|
||||
|
@ -105,9 +105,15 @@ class Store::UserPayOrderTest < ActionDispatch::IntegrationTest
|
||||
assert_not invoice.total.blank?
|
||||
assert invoice.check_footprint
|
||||
|
||||
# notification
|
||||
# invoice notification
|
||||
assert_not_empty Notification.where(attached_object: invoice)
|
||||
|
||||
# order notification
|
||||
assert_not_nil Notification.find_by(
|
||||
notification_type_id: NotificationType.find_by(name: 'notify_admin_order_is_paid'),
|
||||
attached_object: @cart1.order_activities.last
|
||||
)
|
||||
|
||||
assert_equal @cart1.state, 'paid'
|
||||
assert_equal @cart1.payment_method, 'card'
|
||||
assert_equal @cart1.paid_total, 400
|
||||
|
Loading…
x
Reference in New Issue
Block a user