1
0
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:
Sylvain 2023-02-16 11:22:42 +01:00
parent f92a4a741b
commit ed072184bf
20 changed files with 82 additions and 22 deletions

View File

@ -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

View File

@ -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)

View File

@ -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];

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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 })

View File

@ -1 +1,3 @@
<%= render 'notifications_mailer/shared/hello', recipient: @recipient %>
<p><%= t('.body.objects_sync') %></p>

View File

@ -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>

View File

@ -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"

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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