mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
(merge) Merge remote-tracking branch 'origin/product-store_cart' into product-store
This commit is contained in:
commit
3a8082db97
@ -37,6 +37,18 @@ class API::CartController < API::ApiController
|
||||
render 'api/orders/show'
|
||||
end
|
||||
|
||||
def refresh_item
|
||||
authorize @current_order, policy_class: CartPolicy
|
||||
@order = Cart::RefreshItemService.new.call(@current_order, orderable)
|
||||
render 'api/orders/show'
|
||||
end
|
||||
|
||||
def validate
|
||||
authorize @current_order, policy_class: CartPolicy
|
||||
@order_errors = Cart::CheckCartService.new.call(@current_order)
|
||||
render json: @order_errors
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def orderable
|
||||
|
@ -1,6 +1,6 @@
|
||||
import apiClient from './clients/api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Order } from '../models/order';
|
||||
import { Order, OrderErrors } from '../models/order';
|
||||
|
||||
export default class CartAPI {
|
||||
static async create (token?: string): Promise<Order> {
|
||||
@ -27,4 +27,14 @@ export default class CartAPI {
|
||||
const res: AxiosResponse<Order> = await apiClient.put('/api/cart/set_offer', { order_token: order.token, orderable_id: orderableId, is_offered: isOffered });
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async refreshItem (order: Order, orderableId: number): Promise<Order> {
|
||||
const res: AxiosResponse<Order> = await apiClient.put('/api/cart/refresh_item', { order_token: order.token, orderable_id: orderableId });
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async validate (order: Order): Promise<OrderErrors> {
|
||||
const res: AxiosResponse<OrderErrors> = await apiClient.post('/api/cart/validate', { order_token: order.token });
|
||||
return res?.data;
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import CartAPI from '../../api/cart';
|
||||
import { User } from '../../models/user';
|
||||
import { PaymentModal } from '../payment/stripe/payment-modal';
|
||||
import { PaymentMethod } from '../../models/payment';
|
||||
import { Order } from '../../models/order';
|
||||
import { Order, OrderErrors } from '../../models/order';
|
||||
import { MemberSelect } from '../user/member-select';
|
||||
import { CouponInput } from '../coupon/coupon-input';
|
||||
import { Coupon } from '../../models/coupon';
|
||||
@ -20,6 +20,7 @@ import OrderLib from '../../lib/order';
|
||||
import { CaretDown, CaretUp } from 'phosphor-react';
|
||||
import SettingAPI from '../../api/setting';
|
||||
import { SettingName } from '../../models/setting';
|
||||
import _ from 'lodash';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -36,8 +37,8 @@ interface StoreCartProps {
|
||||
const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser, userLogin }) => {
|
||||
const { t } = useTranslation('public');
|
||||
|
||||
const { cart, setCart } = useCart(currentUser);
|
||||
const [itemsQuantity, setItemsQuantity] = useState<{ id: number; quantity: number; }[]>([]);
|
||||
const { cart, setCart, reloadCart } = useCart(currentUser);
|
||||
const [cartErrors, setCartErrors] = useState<OrderErrors>(null);
|
||||
const [paymentModal, setPaymentModal] = useState<boolean>(false);
|
||||
const [settings, setSettings] = useState<Map<SettingName, string>>(null);
|
||||
|
||||
@ -48,10 +49,9 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const quantities = cart?.order_items_attributes.map(i => {
|
||||
return { id: i.id, quantity: i.quantity };
|
||||
});
|
||||
setItemsQuantity(quantities);
|
||||
if (cart) {
|
||||
checkCart();
|
||||
}
|
||||
}, [cart]);
|
||||
|
||||
/**
|
||||
@ -61,9 +61,14 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
||||
return (e: React.BaseSyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
CartAPI.removeItem(cart, item.orderable_id).then(data => {
|
||||
setCart(data);
|
||||
}).catch(onError);
|
||||
const errors = getItemErrors(item);
|
||||
if (errors.length === 1 && errors[0].error === 'not_found') {
|
||||
reloadCart().catch(onError);
|
||||
} else {
|
||||
CartAPI.removeItem(cart, item.orderable_id).then(data => {
|
||||
setCart(data);
|
||||
}).catch(onError);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@ -77,8 +82,11 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
||||
})
|
||||
.catch(() => onError(t('app.public.store_cart.stock_limit')));
|
||||
};
|
||||
/** Increment/decrement product quantity */
|
||||
const handleInputNumber = (item, direction: 'up' | 'down') => {
|
||||
|
||||
/**
|
||||
* Increment/decrement product quantity
|
||||
*/
|
||||
const increaseOrDecreaseProductQuantity = (item, direction: 'up' | 'down') => {
|
||||
CartAPI.setQuantity(cart, item.orderable_id, direction === 'up' ? item.quantity + 1 : item.quantity - 1)
|
||||
.then(data => {
|
||||
setCart(data);
|
||||
@ -86,6 +94,28 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
||||
.catch(() => onError(t('app.public.store_cart.stock_limit')));
|
||||
};
|
||||
|
||||
/**
|
||||
* Refresh product amount
|
||||
*/
|
||||
const refreshItem = (item) => {
|
||||
return (e: React.BaseSyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
CartAPI.refreshItem(cart, item.orderable_id).then(data => {
|
||||
setCart(data);
|
||||
}).catch(onError);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Check the current cart's items (available, price, stock, quantity_min)
|
||||
*/
|
||||
const checkCart = async (): Promise<OrderErrors> => {
|
||||
const errors = await CartAPI.validate(cart);
|
||||
setCartErrors(errors);
|
||||
return errors;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checkout cart
|
||||
*/
|
||||
@ -96,11 +126,36 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
||||
if (!cart.user) {
|
||||
onError(t('app.public.store_cart.select_user'));
|
||||
} else {
|
||||
setPaymentModal(true);
|
||||
checkCart().then(errors => {
|
||||
if (!hasCartErrors(errors)) {
|
||||
setPaymentModal(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the carrent cart has any error
|
||||
*/
|
||||
const hasCartErrors = (errors: OrderErrors) => {
|
||||
if (!errors) return false;
|
||||
for (const item of cart.order_items_attributes) {
|
||||
const error = _.find(errors.details, (e) => e.item_id === item.id);
|
||||
if (!error || error?.errors?.length > 0) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* get givean item's error
|
||||
*/
|
||||
const getItemErrors = (item) => {
|
||||
if (!cartErrors) return [];
|
||||
const errors = _.find(cartErrors.details, (e) => e.item_id === item.id);
|
||||
return errors?.errors || [{ error: 'not_found' }];
|
||||
};
|
||||
|
||||
/**
|
||||
* Open/closes the payment modal
|
||||
*/
|
||||
@ -162,6 +217,30 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show item error
|
||||
*/
|
||||
const itemError = (item, error) => {
|
||||
if (error.error === 'is_active' || error.error === 'not_found') {
|
||||
return <div className='error'><p>{t('app.public.store_cart.errors.product_not_found')}</p></div>;
|
||||
}
|
||||
if (error.error === 'stock' && error.value === 0) {
|
||||
return <div className='error'><p>{t('app.public.store_cart.errors.out_of_stock')}</p></div>;
|
||||
}
|
||||
if (error.error === 'stock' && error.value > 0) {
|
||||
return <div className='error'><p>{t('app.public.store_cart.errors.stock_limit_QUANTITY', { QUANTITY: error.value })}</p></div>;
|
||||
}
|
||||
if (error.error === 'quantity_min') {
|
||||
return <div className='error'><p>{t('app.public.store_cart.errors.quantity_min_QUANTITY', { QUANTITY: error.value })}</p></div>;
|
||||
}
|
||||
if (error.error === 'amount') {
|
||||
return <div className='error'>
|
||||
<p>{t('app.public.store_cart.errors.price_changed_PRICE', { PRICE: `${FormatLib.price(error.value)} / ${t('app.public.store_cart.unit')}` })}</p>
|
||||
<span className='refresh-btn' onClick={refreshItem(item)}>{t('app.public.store_cart.update_item')}</span>
|
||||
</div>;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Text instructions for the customer
|
||||
*/
|
||||
@ -178,7 +257,7 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
||||
<div className="store-cart-list">
|
||||
{cart && cartIsEmpty() && <p>{t('app.public.store_cart.cart_is_empty')}</p>}
|
||||
{cart && cart.order_items_attributes.map(item => (
|
||||
<article key={item.id} className='store-cart-list-item'>
|
||||
<article key={item.id} className={`store-cart-list-item ${getItemErrors(item).length > 0 ? 'error' : ''}`}>
|
||||
<div className='picture'>
|
||||
<img alt='' src={item.orderable_main_image_url || noImage} />
|
||||
</div>
|
||||
@ -188,6 +267,9 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
||||
{item.quantity_min > 1 &&
|
||||
<span className='min'>{t('app.public.store_cart.minimum_purchase')}{item.quantity_min}</span>
|
||||
}
|
||||
{getItemErrors(item).map(e => {
|
||||
return itemError(item, e);
|
||||
})}
|
||||
</div>
|
||||
<div className="actions">
|
||||
<div className='price'>
|
||||
@ -199,10 +281,10 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
||||
onChange={e => changeProductQuantity(e, item)}
|
||||
min={item.quantity_min}
|
||||
max={item.orderable_external_stock}
|
||||
value={itemsQuantity?.find(i => i.id === item.id)?.quantity || 1}
|
||||
value={item.quantity}
|
||||
/>
|
||||
<button onClick={() => handleInputNumber(item, 'up')}><CaretUp size={12} weight="fill" /></button>
|
||||
<button onClick={() => handleInputNumber(item, 'down')}><CaretDown size={12} weight="fill" /></button>
|
||||
<button onClick={() => increaseOrDecreaseProductQuantity(item, 'up')}><CaretUp size={12} weight="fill" /></button>
|
||||
<button onClick={() => increaseOrDecreaseProductQuantity(item, 'down')}><CaretDown size={12} weight="fill" /></button>
|
||||
</div>
|
||||
<div className='total'>
|
||||
<span>{t('app.public.store_cart.total')}</span>
|
||||
|
@ -41,7 +41,7 @@ export default class ProductLib {
|
||||
};
|
||||
|
||||
static stockStatusTrKey = (product: Product): string => {
|
||||
if (product.stock.external <= 0) {
|
||||
if (product.stock.external <= (product.quantity_min || 0)) {
|
||||
return 'app.public.stock_status.out_of_stock';
|
||||
}
|
||||
if (product.low_stock_threshold && product.stock.external < product.low_stock_threshold) {
|
||||
|
@ -63,3 +63,14 @@ export interface OrderIndexFilter extends ApiFilter {
|
||||
period_from?: string,
|
||||
period_to?: string
|
||||
}
|
||||
|
||||
export interface OrderErrors {
|
||||
order_id: number,
|
||||
details: Array<{
|
||||
item_id: number,
|
||||
errors: Array<{
|
||||
error: string,
|
||||
value: string|number
|
||||
}>
|
||||
}>
|
||||
}
|
||||
|
@ -41,19 +41,28 @@
|
||||
margin: 0;
|
||||
@include text-base(600);
|
||||
}
|
||||
.min {
|
||||
.min,.error p {
|
||||
margin-top: 0.8rem;
|
||||
@include text-sm;
|
||||
color: var(--alert);
|
||||
text-transform: none;
|
||||
}
|
||||
.error .refresh-btn {
|
||||
@extend .fab-button, .is-black;
|
||||
height: auto;
|
||||
margin-top: 0.4rem;
|
||||
padding: 0.4rem 0.8rem;
|
||||
@include text-sm;
|
||||
}
|
||||
}
|
||||
.actions {
|
||||
grid-area: 2 / 1 / 3 / 3;
|
||||
align-self: stretch;
|
||||
padding: 0.8rem;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
justify-content: space-between;
|
||||
grid-template-columns: min-content min-content;
|
||||
justify-content: space-evenly;
|
||||
justify-items: flex-end;
|
||||
align-items: center;
|
||||
gap: 2.4rem;
|
||||
background-color: var(--gray-soft-light);
|
||||
@ -87,8 +96,7 @@
|
||||
border-radius: var(--border-radius-sm);
|
||||
input[type="number"] {
|
||||
grid-area: 1 / 1 / 3 / 2;
|
||||
width: 4ch;
|
||||
min-width: fit-content;
|
||||
min-width: 4ch;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
text-align: right;
|
||||
@ -133,11 +141,14 @@
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
&.error {
|
||||
border-color: var(--alert);
|
||||
}
|
||||
}
|
||||
}
|
||||
.group {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2.4rem;
|
||||
}
|
||||
&-info,
|
||||
@ -217,25 +228,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.actions {
|
||||
grid-auto-flow: column;
|
||||
grid-template-columns: 1fr min-content 1fr min-content;
|
||||
justify-content: stretch;
|
||||
justify-items: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
&-list-item {
|
||||
.ref { grid-area: 1 / 2 / 2 / 3; }
|
||||
.actions { grid-area: 2 / 1 / 3 / 4; }
|
||||
.offer { grid-area: 1 / 3 / 2 / 4; }
|
||||
.actions { grid-area: 2 / 1 / 3 / 3; }
|
||||
}
|
||||
.group { grid-template-columns: repeat(2, 1fr); }
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
&-list-item {
|
||||
grid-auto-flow: column;
|
||||
grid-auto-flow: row;
|
||||
grid-template-columns: min-content 1fr 1fr;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.picture { grid-area: auto; }
|
||||
.ref { grid-area: auto; }
|
||||
.actions { grid-area: auto; }
|
||||
.picture { grid-area: 1 / 1 / 2 / 2; }
|
||||
.ref { grid-area: 1 / 2 / 2 / 3; }
|
||||
.actions { grid-area: 1 / 3 / 2 / 4; }
|
||||
.offer {
|
||||
grid-area: auto;
|
||||
align-self: flex-start;
|
||||
grid-area: 2 / 1 / 3 / 4;
|
||||
justify-self: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ class CartPolicy < ApplicationPolicy
|
||||
true
|
||||
end
|
||||
|
||||
%w[add_item remove_item set_quantity].each do |action|
|
||||
%w[add_item remove_item set_quantity refresh_item validate].each do |action|
|
||||
define_method "#{action}?" do
|
||||
return user.privileged? || (record.statistic_profile_id == user.statistic_profile.id) if user
|
||||
|
||||
|
21
app/services/cart/check_cart_service.rb
Normal file
21
app/services/cart/check_cart_service.rb
Normal file
@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Provides methods for check cart's items (available, price, stock, quantity_min)
|
||||
class Cart::CheckCartService
|
||||
def call(order)
|
||||
res = { order_id: order.id, details: [] }
|
||||
order.order_items.each do |item|
|
||||
errors = []
|
||||
errors.push({ error: 'is_active', value: false }) unless item.orderable.is_active
|
||||
if item.quantity > item.orderable.stock['external'] || item.orderable.stock['external'] < item.orderable.quantity_min
|
||||
value = item.orderable.stock['external'] < item.orderable.quantity_min ? 0 : item.orderable.stock['external']
|
||||
errors.push({ error: 'stock', value: value })
|
||||
end
|
||||
orderable_amount = item.orderable.amount || 0
|
||||
errors.push({ error: 'amount', value: orderable_amount / 100.0 }) if item.amount != orderable_amount
|
||||
errors.push({ error: 'quantity_min', value: item.orderable.quantity_min }) if item.quantity < item.orderable.quantity_min
|
||||
res[:details].push({ item_id: item.id, errors: errors })
|
||||
end
|
||||
res
|
||||
end
|
||||
end
|
21
app/services/cart/refresh_item_service.rb
Normal file
21
app/services/cart/refresh_item_service.rb
Normal file
@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Provides methods for refresh amount of order item
|
||||
class Cart::RefreshItemService
|
||||
def call(order, orderable)
|
||||
raise Cart::InactiveProductError unless orderable.is_active
|
||||
|
||||
item = order.order_items.find_by(orderable: orderable)
|
||||
|
||||
raise ActiveRecord::RecordNotFound if item.nil?
|
||||
|
||||
order.total -= (item.amount * item.quantity.to_i) unless item.is_offered
|
||||
item.amount = orderable.amount || 0
|
||||
order.total += (item.amount * item.quantity.to_i) unless item.is_offered
|
||||
ActiveRecord::Base.transaction do
|
||||
item.save
|
||||
order.save
|
||||
end
|
||||
order.reload
|
||||
end
|
||||
end
|
@ -44,7 +44,7 @@ class Orders::OrderService
|
||||
|
||||
def in_stock?(order, stock_type = 'external')
|
||||
order.order_items.each do |item|
|
||||
return false if item.orderable.stock[stock_type] < item.quantity
|
||||
return false if item.orderable.stock[stock_type] < item.quantity || item.orderable.stock[stock_type] < item.orderable.quantity_min
|
||||
end
|
||||
true
|
||||
end
|
||||
@ -58,7 +58,8 @@ class Orders::OrderService
|
||||
|
||||
def item_amount_not_equal?(order)
|
||||
order.order_items.each do |item|
|
||||
return false if item.amount != item.orderable.amount
|
||||
orderable_amount = item.orderable.amount || 0
|
||||
return false if item.amount != orderable_amount
|
||||
end
|
||||
true
|
||||
end
|
||||
|
@ -443,6 +443,13 @@ en:
|
||||
checkout_success: "Purchase confirmed. Thanks!"
|
||||
select_user: "Please select a user before continuing."
|
||||
please_contact_FABLAB: "Please contact {FABLAB, select, undefined{us} other{{FABLAB}}} for withdrawal instructions."
|
||||
update_item: "Update"
|
||||
errors:
|
||||
product_not_found: "This product is no longer available, please remove it from your cart."
|
||||
out_of_stock: "This product is out of stock, please remove it from your cart."
|
||||
stock_limit_QUANTITY: "Only {QUANTITY} {QUANTITY, plural, =1{unit} other{units}} left in stock, please adjust the quantity of items."
|
||||
quantity_min_QUANTITY: "Minimum number of product was changed to {QUANTITY}, please adjust the quantity of items."
|
||||
price_changed_PRICE: "The product price was modified to {PRICE}"
|
||||
orders_dashboard:
|
||||
heading: "My orders"
|
||||
sort:
|
||||
|
@ -162,6 +162,8 @@ Rails.application.routes.draw do
|
||||
put 'remove_item', on: :collection
|
||||
put 'set_quantity', on: :collection
|
||||
put 'set_offer', on: :collection
|
||||
put 'refresh_item', on: :collection
|
||||
post 'validate', on: :collection
|
||||
end
|
||||
resources :checkout, only: %i[] do
|
||||
post 'payment', on: :collection
|
||||
|
20
db/schema.rb
20
db/schema.rb
@ -19,8 +19,8 @@ ActiveRecord::Schema.define(version: 2022_10_03_133019) do
|
||||
enable_extension "unaccent"
|
||||
|
||||
create_table "abuses", id: :serial, force: :cascade do |t|
|
||||
t.integer "signaled_id"
|
||||
t.string "signaled_type"
|
||||
t.integer "signaled_id"
|
||||
t.string "first_name"
|
||||
t.string "last_name"
|
||||
t.string "email"
|
||||
@ -49,8 +49,8 @@ ActiveRecord::Schema.define(version: 2022_10_03_133019) do
|
||||
t.string "locality"
|
||||
t.string "country"
|
||||
t.string "postal_code"
|
||||
t.integer "placeable_id"
|
||||
t.string "placeable_type"
|
||||
t.integer "placeable_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
end
|
||||
@ -64,8 +64,8 @@ ActiveRecord::Schema.define(version: 2022_10_03_133019) do
|
||||
end
|
||||
|
||||
create_table "assets", id: :serial, force: :cascade do |t|
|
||||
t.integer "viewable_id"
|
||||
t.string "viewable_type"
|
||||
t.integer "viewable_id"
|
||||
t.string "attachment"
|
||||
t.string "type"
|
||||
t.datetime "created_at"
|
||||
@ -147,8 +147,8 @@ ActiveRecord::Schema.define(version: 2022_10_03_133019) do
|
||||
end
|
||||
|
||||
create_table "credits", id: :serial, force: :cascade do |t|
|
||||
t.integer "creditable_id"
|
||||
t.string "creditable_type"
|
||||
t.integer "creditable_id"
|
||||
t.integer "plan_id"
|
||||
t.integer "hours"
|
||||
t.datetime "created_at"
|
||||
@ -375,15 +375,15 @@ ActiveRecord::Schema.define(version: 2022_10_03_133019) do
|
||||
|
||||
create_table "notifications", id: :serial, force: :cascade do |t|
|
||||
t.integer "receiver_id"
|
||||
t.integer "attached_object_id"
|
||||
t.string "attached_object_type"
|
||||
t.integer "attached_object_id"
|
||||
t.integer "notification_type_id"
|
||||
t.boolean "is_read", default: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "receiver_type"
|
||||
t.boolean "is_send", default: false
|
||||
t.jsonb "meta_data", default: {}
|
||||
t.jsonb "meta_data", default: "{}"
|
||||
t.index ["notification_type_id"], name: "index_notifications_on_notification_type_id"
|
||||
t.index ["receiver_id"], name: "index_notifications_on_receiver_id"
|
||||
end
|
||||
@ -623,8 +623,8 @@ ActiveRecord::Schema.define(version: 2022_10_03_133019) do
|
||||
create_table "prices", id: :serial, force: :cascade do |t|
|
||||
t.integer "group_id"
|
||||
t.integer "plan_id"
|
||||
t.integer "priceable_id"
|
||||
t.string "priceable_type"
|
||||
t.integer "priceable_id"
|
||||
t.integer "amount"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
@ -824,8 +824,8 @@ ActiveRecord::Schema.define(version: 2022_10_03_133019) do
|
||||
t.text "message"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "reservable_id"
|
||||
t.string "reservable_type"
|
||||
t.integer "reservable_id"
|
||||
t.integer "nb_reserve_places"
|
||||
t.integer "statistic_profile_id"
|
||||
t.index ["reservable_type", "reservable_id"], name: "index_reservations_on_reservable_type_and_reservable_id"
|
||||
@ -834,8 +834,8 @@ ActiveRecord::Schema.define(version: 2022_10_03_133019) do
|
||||
|
||||
create_table "roles", id: :serial, force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.integer "resource_id"
|
||||
t.string "resource_type"
|
||||
t.integer "resource_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id"
|
||||
@ -1115,8 +1115,8 @@ ActiveRecord::Schema.define(version: 2022_10_03_133019) do
|
||||
t.boolean "is_allow_newsletter"
|
||||
t.inet "current_sign_in_ip"
|
||||
t.inet "last_sign_in_ip"
|
||||
t.string "mapped_from_sso"
|
||||
t.datetime "validated_at"
|
||||
t.string "mapped_from_sso"
|
||||
t.index ["auth_token"], name: "index_users_on_auth_token"
|
||||
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
|
||||
t.index ["email"], name: "index_users_on_email", unique: true
|
||||
|
Loading…
x
Reference in New Issue
Block a user