mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
store cart
This commit is contained in:
parent
16288ae2bd
commit
ab800a519f
54
app/controllers/api/cart_controller.rb
Normal file
54
app/controllers/api/cart_controller.rb
Normal file
@ -0,0 +1,54 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# API Controller for manage user's cart
|
||||
class API::CartController < API::ApiController
|
||||
before_action :current_order
|
||||
before_action :ensure_order, except: %i[create]
|
||||
|
||||
def create
|
||||
authorize :cart, :create?
|
||||
@order = current_order if current_order.present?
|
||||
@order ||= Cart::CreateService.new.call(current_user)
|
||||
render 'api/orders/show'
|
||||
end
|
||||
|
||||
def add_item
|
||||
authorize @current_order, policy_class: CartPolicy
|
||||
@order = Cart::AddItemService.new.call(@current_order, orderable, cart_params[:quantity])
|
||||
render 'api/orders/show'
|
||||
end
|
||||
|
||||
def remove_item
|
||||
authorize :cart, policy_class: CartPolicy
|
||||
@order = Cart::RemoveItemService.new.call(@current_order, orderable)
|
||||
render 'api/orders/show'
|
||||
end
|
||||
|
||||
def set_quantity
|
||||
authorize :cart, policy_class: CartPolicy
|
||||
@order = Cart::SetQuantityService.new.call(@current_order, orderable, cart_params[:quantity])
|
||||
render 'api/orders/show'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def order_token
|
||||
request.headers['X-Fablab-Order-Token'] || cart_params[:order_token]
|
||||
end
|
||||
|
||||
def current_order
|
||||
@current_order = Order.find_by(token: order_token)
|
||||
end
|
||||
|
||||
def ensure_order
|
||||
raise ActiveRecord::RecordNotFound if @current_order.nil?
|
||||
end
|
||||
|
||||
def orderable
|
||||
Product.find(cart_params[:orderable_id])
|
||||
end
|
||||
|
||||
def cart_params
|
||||
params.permit(:order_token, :orderable_id, :quantity)
|
||||
end
|
||||
end
|
5
app/exceptions/cart/inactive_product_error.rb
Normal file
5
app/exceptions/cart/inactive_product_error.rb
Normal file
@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Raised when the product is out of stock
|
||||
class Cart::InactiveProductError < StandardError
|
||||
end
|
5
app/exceptions/cart/out_stock_error.rb
Normal file
5
app/exceptions/cart/out_stock_error.rb
Normal file
@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Raised when the product is out of stock
|
||||
class Cart::OutStockError < StandardError
|
||||
end
|
25
app/frontend/src/javascript/api/cart.ts
Normal file
25
app/frontend/src/javascript/api/cart.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import apiClient from './clients/api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Order } from '../models/order';
|
||||
|
||||
export default class CartAPI {
|
||||
static async create (token?: string): Promise<Order> {
|
||||
const res: AxiosResponse<Order> = await apiClient.post('/api/cart', { order_token: token });
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async addItem (order: Order, orderableId: number, quantity: number): Promise<Order> {
|
||||
const res: AxiosResponse<Order> = await apiClient.put('/api/cart/add_item', { order_token: order.token, orderable_id: orderableId, quantity });
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async removeItem (order: Order, orderableId: number): Promise<Order> {
|
||||
const res: AxiosResponse<Order> = await apiClient.put('/api/cart/remove_item', { order_token: order.token, orderable_id: orderableId });
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async setQuantity (order: Order, orderableId: number, quantity: number): Promise<Order> {
|
||||
const res: AxiosResponse<Order> = await apiClient.put('/api/cart/set_quantity', { order_token: order.token, orderable_id: orderableId, quantity });
|
||||
return res?.data;
|
||||
}
|
||||
}
|
65
app/frontend/src/javascript/components/cart/store-cart.tsx
Normal file
65
app/frontend/src/javascript/components/cart/store-cart.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { Loader } from '../base/loader';
|
||||
import { IApplication } from '../../models/application';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import useCart from '../../hooks/use-cart';
|
||||
import FormatLib from '../../lib/format';
|
||||
import CartAPI from '../../api/cart';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
interface StoreCartProps {
|
||||
onError: (message: string) => void,
|
||||
}
|
||||
|
||||
/**
|
||||
* This component shows user's cart
|
||||
*/
|
||||
const StoreCart: React.FC<StoreCartProps> = ({ onError }) => {
|
||||
const { t } = useTranslation('public');
|
||||
|
||||
const { loading, cart, setCart } = useCart();
|
||||
|
||||
/**
|
||||
* Remove the product from cart
|
||||
*/
|
||||
const removeProductFromCart = (item) => {
|
||||
return (e: React.BaseSyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
CartAPI.removeItem(cart, item.orderable_id).then(data => {
|
||||
setCart(data);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="store-cart">
|
||||
{loading && <p>loading</p>}
|
||||
{cart && cart.order_items_attributes.map(item => (
|
||||
<div key={item.id}>
|
||||
<div>{item.orderable_name}</div>
|
||||
<div>{FormatLib.price(item.amount)}</div>
|
||||
<div>{item.quantity}</div>
|
||||
<div>{FormatLib.price(item.quantity * item.amount)}</div>
|
||||
<FabButton className="delete-btn" onClick={removeProductFromCart(item)}>
|
||||
<i className="fa fa-trash" /> {t('app.public.store_cart.remove_item')}
|
||||
</FabButton>
|
||||
</div>
|
||||
))}
|
||||
{cart && <p>{cart.amount}</p>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const StoreCartWrapper: React.FC<StoreCartProps> = ({ onError }) => {
|
||||
return (
|
||||
<Loader>
|
||||
<StoreCart onError={onError} />
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
Application.Components.component('storeCart', react2angular(StoreCartWrapper, ['onError']));
|
@ -3,16 +3,19 @@ import { useTranslation } from 'react-i18next';
|
||||
import _ from 'lodash';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { Product } from '../../models/product';
|
||||
import { Order } from '../../models/order';
|
||||
import FormatLib from '../../lib/format';
|
||||
import CartAPI from '../../api/cart';
|
||||
|
||||
interface StoreProductItemProps {
|
||||
product: Product,
|
||||
cart: Order,
|
||||
}
|
||||
|
||||
/**
|
||||
* This component shows a product item in store
|
||||
*/
|
||||
export const StoreProductItem: React.FC<StoreProductItemProps> = ({ product }) => {
|
||||
export const StoreProductItem: React.FC<StoreProductItemProps> = ({ product, cart }) => {
|
||||
const { t } = useTranslation('public');
|
||||
|
||||
/**
|
||||
@ -36,6 +39,15 @@ export const StoreProductItem: React.FC<StoreProductItemProps> = ({ product }) =
|
||||
return <span>{t('app.public.store_product_item.available')}</span>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the product to cart
|
||||
*/
|
||||
const addProductToCart = (e: React.BaseSyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
CartAPI.addItem(cart, product.id, 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Goto show product page
|
||||
*/
|
||||
@ -54,7 +66,7 @@ export const StoreProductItem: React.FC<StoreProductItemProps> = ({ product }) =
|
||||
<div>{FormatLib.price(product.amount)}</div>
|
||||
{productStockStatus(product)}
|
||||
</span>
|
||||
<FabButton className='edit-btn'>
|
||||
<FabButton className="edit-btn" onClick={addProductToCart}>
|
||||
<i className="fas fa-cart-arrow-down" /> {t('app.public.store_product_item.add')}
|
||||
</FabButton>
|
||||
</div>
|
||||
|
@ -7,6 +7,7 @@ import { FabButton } from '../base/fab-button';
|
||||
import { Product } from '../../models/product';
|
||||
import ProductAPI from '../../api/product';
|
||||
import { StoreProductItem } from './store-product-item';
|
||||
import useCart from '../../hooks/use-cart';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -20,6 +21,8 @@ interface StoreProps {
|
||||
const Store: React.FC<StoreProps> = ({ onError }) => {
|
||||
const { t } = useTranslation('public');
|
||||
|
||||
const { cart } = useCart();
|
||||
|
||||
const [products, setProducts] = useState<Array<Product>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -68,7 +71,7 @@ const Store: React.FC<StoreProps> = ({ onError }) => {
|
||||
|
||||
<div className="products">
|
||||
{products.map((product) => (
|
||||
<StoreProductItem key={product.id} product={product} />
|
||||
<StoreProductItem key={product.id} product={product} cart={cart}/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
41
app/frontend/src/javascript/controllers/cart.js
Normal file
41
app/frontend/src/javascript/controllers/cart.js
Normal file
@ -0,0 +1,41 @@
|
||||
/* eslint-disable
|
||||
no-return-assign,
|
||||
no-undef,
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
Application.Controllers.controller('CartController', ['$scope', 'CSRF', 'growl', '$state',
|
||||
function ($scope, CSRF, growl, $state) {
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
/**
|
||||
* Callback triggered in case of error
|
||||
*/
|
||||
$scope.onError = (message) => {
|
||||
growl.error(message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered in case of success
|
||||
*/
|
||||
$scope.onSuccess = (message) => {
|
||||
growl.success(message);
|
||||
};
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
* Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
*/
|
||||
const initialize = function () {
|
||||
// set the authenticity tokens in the forms
|
||||
CSRF.setMetaTags();
|
||||
};
|
||||
|
||||
// init the controller (call at the end !)
|
||||
return initialize();
|
||||
}
|
||||
|
||||
]);
|
29
app/frontend/src/javascript/hooks/use-cart.ts
Normal file
29
app/frontend/src/javascript/hooks/use-cart.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Order } from '../models/order';
|
||||
import CartAPI from '../api/cart';
|
||||
import { getCartToken, setCartToken } from '../lib/cart-token';
|
||||
|
||||
export default function useCart () {
|
||||
const [cart, setCart] = useState<Order>(null);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function createCart () {
|
||||
const currentCartToken = getCartToken();
|
||||
const data = await CartAPI.create(currentCartToken);
|
||||
setCart(data);
|
||||
setLoading(false);
|
||||
setCartToken(data.token);
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
createCart();
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
setError(e);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { loading, cart, error, setCart };
|
||||
}
|
23
app/frontend/src/javascript/lib/cart-token.ts
Normal file
23
app/frontend/src/javascript/lib/cart-token.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
export const cartCookieName = 'fablab_cart_token';
|
||||
export const cartCookieExpire = 7;
|
||||
|
||||
export const getCartToken = () =>
|
||||
Cookies.get(cartCookieName);
|
||||
|
||||
export const setCartToken = (cartToken: string) => {
|
||||
const cookieOptions = {
|
||||
expires: cartCookieExpire
|
||||
};
|
||||
|
||||
Cookies.set(
|
||||
cartCookieName,
|
||||
cartToken,
|
||||
cookieOptions
|
||||
);
|
||||
};
|
||||
|
||||
export const removeCartToken = () => {
|
||||
Cookies.remove(cartCookieName);
|
||||
};
|
21
app/frontend/src/javascript/models/order.ts
Normal file
21
app/frontend/src/javascript/models/order.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { TDateISO } from '../typings/date-iso';
|
||||
|
||||
export interface Order {
|
||||
id: number,
|
||||
token: string,
|
||||
statistic_profile_id?: number,
|
||||
operator_id?: number,
|
||||
reference?: string,
|
||||
state?: string,
|
||||
amount?: number,
|
||||
created_at?: TDateISO,
|
||||
order_items_attributes: Array<{
|
||||
id: number,
|
||||
orderable_type: string,
|
||||
orderable_id: number,
|
||||
orderable_name: string,
|
||||
quantity: number,
|
||||
amount: number,
|
||||
is_offered: boolean
|
||||
}>,
|
||||
}
|
@ -622,6 +622,17 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
})
|
||||
|
||||
// cart
|
||||
.state('app.public.cart', {
|
||||
url: '/cart',
|
||||
views: {
|
||||
'main@': {
|
||||
templateUrl: '/cart/index.html',
|
||||
controller: 'CartController'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// --- namespace /admin/... ---
|
||||
// calendar
|
||||
.state('app.admin.calendar', {
|
||||
|
19
app/frontend/templates/cart/index.html
Normal file
19
app/frontend/templates/cart/index.html
Normal file
@ -0,0 +1,19 @@
|
||||
<section class="heading b-b">
|
||||
<div class="row no-gutter">
|
||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||
<section class="heading-btn">
|
||||
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'app.public.cart.my_cart' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="m-lg">
|
||||
<store-cart on-error="onError" on-success="onSuccess" />
|
||||
</section>
|
12
app/models/order.rb
Normal file
12
app/models/order.rb
Normal file
@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Order is a model for the user hold information of order
|
||||
class Order < ApplicationRecord
|
||||
belongs_to :statistic_profile
|
||||
has_many :order_items, dependent: :destroy
|
||||
|
||||
ALL_STATES = %w[cart].freeze
|
||||
enum state: ALL_STATES.zip(ALL_STATES).to_h
|
||||
|
||||
validates :token, :state, presence: true
|
||||
end
|
9
app/models/order_item.rb
Normal file
9
app/models/order_item.rb
Normal file
@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# A single line inside an Order. Can be an article of Order
|
||||
class OrderItem < ApplicationRecord
|
||||
belongs_to :order
|
||||
belongs_to :orderable, polymorphic: true
|
||||
|
||||
validates :orderable, :order_id, :amount, presence: true
|
||||
end
|
14
app/policies/cart_policy.rb
Normal file
14
app/policies/cart_policy.rb
Normal file
@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Check the access policies for API::CartController
|
||||
class CartPolicy < ApplicationPolicy
|
||||
def create?
|
||||
true
|
||||
end
|
||||
|
||||
%w[add_item remove_item set_quantity].each do |action|
|
||||
define_method "#{action}?" do
|
||||
user.privileged? || (record.statistic_profile.user_id == user.id)
|
||||
end
|
||||
end
|
||||
end
|
25
app/services/cart/add_item_service.rb
Normal file
25
app/services/cart/add_item_service.rb
Normal file
@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Provides methods for add order item to cart
|
||||
class Cart::AddItemService
|
||||
def call(order, orderable, quantity = 1)
|
||||
return order if quantity.to_i.zero?
|
||||
|
||||
raise Cart::InactiveProductError unless orderable.is_active
|
||||
|
||||
raise Cart::OutStockError if quantity > orderable.stock['external']
|
||||
|
||||
item = order.order_items.find_by(orderable: orderable)
|
||||
if item.nil?
|
||||
item = order.order_items.new(quantity: quantity, orderable: orderable, amount: orderable.amount)
|
||||
else
|
||||
item.quantity += quantity.to_i
|
||||
end
|
||||
order.amount += (orderable.amount * quantity.to_i)
|
||||
ActiveRecord::Base.transaction do
|
||||
item.save
|
||||
order.save
|
||||
end
|
||||
order.reload
|
||||
end
|
||||
end
|
19
app/services/cart/create_service.rb
Normal file
19
app/services/cart/create_service.rb
Normal file
@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Provides methods for create cart
|
||||
class Cart::CreateService
|
||||
def call(user)
|
||||
token = GenerateTokenService.new.call(Order)
|
||||
order_param = {
|
||||
token: token,
|
||||
state: 'cart',
|
||||
amount: 0
|
||||
}
|
||||
if user
|
||||
order_param[:statistic_profile_id] = user.statistic_profile.id if user.member?
|
||||
|
||||
order_param[:operator_id] = user.id if user.privileged?
|
||||
end
|
||||
Order.create!(order_param)
|
||||
end
|
||||
end
|
17
app/services/cart/remove_item_service.rb
Normal file
17
app/services/cart/remove_item_service.rb
Normal file
@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Provides methods for remove order item to cart
|
||||
class Cart::RemoveItemService
|
||||
def call(order, orderable)
|
||||
item = order.order_items.find_by(orderable: orderable)
|
||||
|
||||
raise ActiveRecord::RecordNotFound if item.nil?
|
||||
|
||||
order.amount -= (item.amount * item.quantity.to_i)
|
||||
ActiveRecord::Base.transaction do
|
||||
item.destroy!
|
||||
order.save
|
||||
end
|
||||
order.reload
|
||||
end
|
||||
end
|
22
app/services/cart/set_quantity_service.rb
Normal file
22
app/services/cart/set_quantity_service.rb
Normal file
@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Provides methods for update quantity of order item
|
||||
class Cart::SetQuantityService
|
||||
def call(order, orderable, quantity = nil)
|
||||
return order if quantity.to_i.zero?
|
||||
|
||||
raise Cart::OutStockError if quantity > orderable.stock['external']
|
||||
|
||||
item = order.order_items.find_by(orderable: orderable)
|
||||
|
||||
raise ActiveRecord::RecordNotFound if item.nil?
|
||||
|
||||
different_quantity = item.quantity - quantiy.to_i
|
||||
order.amount += (orderable.amount * different_quantity)
|
||||
ActiveRecord::Base.transaction do
|
||||
item.update(quantity: quantity)
|
||||
order.save
|
||||
end
|
||||
order.reload
|
||||
end
|
||||
end
|
21
app/services/generate_token_service.rb
Normal file
21
app/services/generate_token_service.rb
Normal file
@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Generate a unique token
|
||||
class GenerateTokenService
|
||||
def call(model_class = Order)
|
||||
loop do
|
||||
token = "#{random_token}#{unique_ending}"
|
||||
break token unless model_class.exists?(token: token)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def random_token
|
||||
SecureRandom.urlsafe_base64(nil, false)
|
||||
end
|
||||
|
||||
def unique_ending
|
||||
(Time.now.to_f * 1000).to_i
|
||||
end
|
||||
end
|
14
app/views/api/orders/_order.json.jbuilder
Normal file
14
app/views/api/orders/_order.json.jbuilder
Normal file
@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
json.extract! order, :id, :token, :statistic_profile_id, :operator_id, :reference, :state, :created_at
|
||||
json.amount order.amount / 100.0 if order.amount.present?
|
||||
|
||||
json.order_items_attributes order.order_items do |item|
|
||||
json.id item.id
|
||||
json.orderable_type item.orderable_type
|
||||
json.orderable_id item.orderable_id
|
||||
json.orderable_name item.orderable.name
|
||||
json.quantity item.quantity
|
||||
json.amount item.amount / 100.0
|
||||
json.is_offered item.is_offered
|
||||
end
|
3
app/views/api/orders/show.json.jbuilder
Normal file
3
app/views/api/orders/show.json.jbuilder
Normal file
@ -0,0 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
json.partial! 'api/orders/order', order: @order
|
@ -384,6 +384,8 @@ en:
|
||||
add: "Add"
|
||||
store_product:
|
||||
unexpected_error_occurred: "An unexpected error occurred. Please try again later."
|
||||
cart:
|
||||
my_cart: "My Cart"
|
||||
tour:
|
||||
conclusion:
|
||||
title: "Thank you for your attention"
|
||||
|
@ -384,6 +384,8 @@ fr:
|
||||
add: "Ajouter"
|
||||
store_product:
|
||||
unexpected_error_occurred: "An unexpected error occurred. Please try again later."
|
||||
cart:
|
||||
my_cart: "Mon Panier"
|
||||
tour:
|
||||
conclusion:
|
||||
title: "Merci de votre attention"
|
||||
|
@ -155,6 +155,11 @@ Rails.application.routes.draw do
|
||||
end
|
||||
|
||||
resources :products
|
||||
resources :cart, only: %i[create] do
|
||||
put 'add_item', on: :collection
|
||||
put 'remove_item', on: :collection
|
||||
put 'set_quantity', on: :collection
|
||||
end
|
||||
|
||||
# for admin
|
||||
resources :trainings do
|
||||
@ -268,7 +273,7 @@ Rails.application.routes.draw do
|
||||
post '/stats/global/export', to: 'api/statistics#export_global'
|
||||
post '_search/scroll', to: 'api/statistics#scroll'
|
||||
|
||||
match '/project_collaborator/:valid_token', to: 'api/projects#collaborator_valid', via: :get
|
||||
get '/project_collaborator/:valid_token', to: 'api/projects#collaborator_valid'
|
||||
|
||||
authenticate :user, ->(u) { u.admin? } do
|
||||
mount Sidekiq::Web => '/admin/sidekiq'
|
||||
|
14
db/migrate/20220808161314_create_orders.rb
Normal file
14
db/migrate/20220808161314_create_orders.rb
Normal file
@ -0,0 +1,14 @@
|
||||
class CreateOrders < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :orders do |t|
|
||||
t.belongs_to :statistic_profile, foreign_key: true
|
||||
t.integer :operator_id
|
||||
t.string :token
|
||||
t.string :reference
|
||||
t.string :state
|
||||
t.integer :amount
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
16
db/migrate/20220818160821_create_order_items.rb
Normal file
16
db/migrate/20220818160821_create_order_items.rb
Normal file
@ -0,0 +1,16 @@
|
||||
# frozen_string_literal:true
|
||||
|
||||
# OrderItem for save article of Order
|
||||
class CreateOrderItems < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :order_items do |t|
|
||||
t.belongs_to :order, foreign_key: true
|
||||
t.references :orderable, polymorphic: true
|
||||
t.integer :amount
|
||||
t.integer :quantity
|
||||
t.boolean :is_offered
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
29
db/schema.rb
29
db/schema.rb
@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2022_08_05_083431) do
|
||||
ActiveRecord::Schema.define(version: 2022_08_18_160821) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "fuzzystrmatch"
|
||||
@ -445,6 +445,31 @@ ActiveRecord::Schema.define(version: 2022_08_05_083431) do
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "order_items", force: :cascade do |t|
|
||||
t.bigint "order_id"
|
||||
t.string "orderable_type"
|
||||
t.bigint "orderable_id"
|
||||
t.integer "amount"
|
||||
t.integer "quantity"
|
||||
t.boolean "is_offered"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["order_id"], name: "index_order_items_on_order_id"
|
||||
t.index ["orderable_type", "orderable_id"], name: "index_order_items_on_orderable_type_and_orderable_id"
|
||||
end
|
||||
|
||||
create_table "orders", force: :cascade do |t|
|
||||
t.bigint "statistic_profile_id"
|
||||
t.integer "operator_id"
|
||||
t.string "token"
|
||||
t.string "reference"
|
||||
t.string "state"
|
||||
t.integer "amount"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["statistic_profile_id"], name: "index_orders_on_statistic_profile_id"
|
||||
end
|
||||
|
||||
create_table "organizations", id: :serial, force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.datetime "created_at", null: false
|
||||
@ -1133,6 +1158,8 @@ ActiveRecord::Schema.define(version: 2022_08_05_083431) do
|
||||
add_foreign_key "invoices", "statistic_profiles"
|
||||
add_foreign_key "invoices", "wallet_transactions"
|
||||
add_foreign_key "invoicing_profiles", "users"
|
||||
add_foreign_key "order_items", "orders"
|
||||
add_foreign_key "orders", "statistic_profiles"
|
||||
add_foreign_key "organizations", "invoicing_profiles"
|
||||
add_foreign_key "payment_gateway_objects", "payment_gateway_objects"
|
||||
add_foreign_key "payment_schedule_items", "invoices"
|
||||
|
@ -121,6 +121,7 @@
|
||||
"jasny-bootstrap": "3.1",
|
||||
"jquery": ">=3.5.0",
|
||||
"jquery-ujs": "^1.2.2",
|
||||
"js-cookie": "^3.0.1",
|
||||
"medium-editor": "^5.23.3",
|
||||
"mini-css-extract-plugin": "^2.6.0",
|
||||
"moment": "2.29",
|
||||
|
@ -5272,6 +5272,11 @@ jquery@>=3.5.0:
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470"
|
||||
integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==
|
||||
|
||||
js-cookie@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414"
|
||||
integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
|
Loading…
x
Reference in New Issue
Block a user