mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-30 19:52:20 +01:00
(feat) client can show orders in dashbaord
This commit is contained in:
parent
dff0cb26be
commit
dbe4570c30
40
app/controllers/api/orders_controller.rb
Normal file
40
app/controllers/api/orders_controller.rb
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# API Controller for resources of type Order
|
||||||
|
# Orders are used in store
|
||||||
|
class API::OrdersController < API::ApiController
|
||||||
|
before_action :authenticate_user!
|
||||||
|
before_action :set_order, only: %i[show update destroy]
|
||||||
|
|
||||||
|
def index
|
||||||
|
@result = ::Orders::OrderService.list(params, current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def show; end
|
||||||
|
|
||||||
|
def update
|
||||||
|
authorize @order
|
||||||
|
|
||||||
|
if @order.update(order_parameters)
|
||||||
|
render status: :ok
|
||||||
|
else
|
||||||
|
render json: @order.errors.full_messages, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
authorize @order
|
||||||
|
@order.destroy
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_order
|
||||||
|
@order = Order.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def order_params
|
||||||
|
params.require(:order).permit(:state)
|
||||||
|
end
|
||||||
|
end
|
16
app/frontend/src/javascript/api/order.ts
Normal file
16
app/frontend/src/javascript/api/order.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import apiClient from './clients/api-client';
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
import { Order, OrderIndexFilter, OrderIndex } from '../models/order';
|
||||||
|
import ApiLib from '../lib/api';
|
||||||
|
|
||||||
|
export default class ProductAPI {
|
||||||
|
static async index (filters?: OrderIndexFilter): Promise<OrderIndex> {
|
||||||
|
const res: AxiosResponse<OrderIndex> = await apiClient.get(`/api/orders${ApiLib.filtersToQuery(filters)}`);
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async get (id: number | string): Promise<Order> {
|
||||||
|
const res: AxiosResponse<Order> = await apiClient.get(`/api/orders/${id}`);
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
|
}
|
@ -14,9 +14,9 @@ import { Order } from '../../models/order';
|
|||||||
import { MemberSelect } from '../user/member-select';
|
import { MemberSelect } from '../user/member-select';
|
||||||
import { CouponInput } from '../coupon/coupon-input';
|
import { CouponInput } from '../coupon/coupon-input';
|
||||||
import { Coupon } from '../../models/coupon';
|
import { Coupon } from '../../models/coupon';
|
||||||
import { computePriceWithCoupon } from '../../lib/coupon';
|
|
||||||
import noImage from '../../../../images/no_image.png';
|
import noImage from '../../../../images/no_image.png';
|
||||||
import Switch from 'react-switch';
|
import Switch from 'react-switch';
|
||||||
|
import OrderLib from '../../lib/order';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
@ -132,52 +132,6 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the item total
|
|
||||||
*/
|
|
||||||
const itemAmount = (item): number => {
|
|
||||||
return item.quantity * Math.trunc(item.amount * 100) / 100;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* return true if cart has offered item
|
|
||||||
*/
|
|
||||||
const hasOfferedItem = (): boolean => {
|
|
||||||
return cart.order_items_attributes
|
|
||||||
.filter(i => i.is_offered).length > 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the offered item total
|
|
||||||
*/
|
|
||||||
const offeredAmount = (): number => {
|
|
||||||
return cart.order_items_attributes
|
|
||||||
.filter(i => i.is_offered)
|
|
||||||
.map(i => Math.trunc(i.amount * 100) * i.quantity)
|
|
||||||
.reduce((acc, curr) => acc + curr, 0) / 100;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the total amount before offered amount
|
|
||||||
*/
|
|
||||||
const totalBeforeOfferedAmount = (): number => {
|
|
||||||
return (Math.trunc(cart.total * 100) + Math.trunc(offeredAmount() * 100)) / 100;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the coupon amount
|
|
||||||
*/
|
|
||||||
const couponAmount = (): number => {
|
|
||||||
return (Math.trunc(cart.total * 100) - Math.trunc(computePriceWithCoupon(cart.total, cart.coupon) * 100)) / 100.00;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the paid total amount
|
|
||||||
*/
|
|
||||||
const paidTotal = (): number => {
|
|
||||||
return computePriceWithCoupon(cart.total, cart.coupon);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='store-cart'>
|
<div className='store-cart'>
|
||||||
<div className="store-cart-list">
|
<div className="store-cart-list">
|
||||||
@ -185,7 +139,7 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
|||||||
{cart && cart.order_items_attributes.map(item => (
|
{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'>
|
||||||
<div className='picture'>
|
<div className='picture'>
|
||||||
<img alt=''src={noImage} />
|
<img alt=''src={item.orderable_main_image_url || noImage} />
|
||||||
</div>
|
</div>
|
||||||
<div className="ref">
|
<div className="ref">
|
||||||
<span>{t('app.public.store_cart.reference_short')} </span>
|
<span>{t('app.public.store_cart.reference_short')} </span>
|
||||||
@ -203,7 +157,7 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
|||||||
</select>
|
</select>
|
||||||
<div className='total'>
|
<div className='total'>
|
||||||
<span>{t('app.public.store_cart.total')}</span>
|
<span>{t('app.public.store_cart.total')}</span>
|
||||||
<p>{FormatLib.price(itemAmount(item))}</p>
|
<p>{FormatLib.price(OrderLib.itemAmount(item))}</p>
|
||||||
</div>
|
</div>
|
||||||
<FabButton className="main-action-btn" onClick={removeProductFromCart(item)}>
|
<FabButton className="main-action-btn" onClick={removeProductFromCart(item)}>
|
||||||
<i className="fa fa-trash" />
|
<i className="fa fa-trash" />
|
||||||
@ -251,15 +205,15 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
|
|||||||
<h3>{t('app.public.store_cart.checkout_header')}</h3>
|
<h3>{t('app.public.store_cart.checkout_header')}</h3>
|
||||||
<span>{t('app.public.store_cart.checkout_products_COUNT', { COUNT: cart?.order_items_attributes.length })}</span>
|
<span>{t('app.public.store_cart.checkout_products_COUNT', { COUNT: cart?.order_items_attributes.length })}</span>
|
||||||
<div className="list">
|
<div className="list">
|
||||||
<p>{t('app.public.store_cart.checkout_products_total')} <span>{FormatLib.price(totalBeforeOfferedAmount())}</span></p>
|
<p>{t('app.public.store_cart.checkout_products_total')} <span>{FormatLib.price(OrderLib.totalBeforeOfferedAmount(cart))}</span></p>
|
||||||
{hasOfferedItem() &&
|
{OrderLib.hasOfferedItem(cart) &&
|
||||||
<p className='gift'>{t('app.public.store_cart.checkout_gift_total')} <span>-{FormatLib.price(offeredAmount())}</span></p>
|
<p className='gift'>{t('app.public.store_cart.checkout_gift_total')} <span>-{FormatLib.price(OrderLib.offeredAmount(cart))}</span></p>
|
||||||
}
|
}
|
||||||
{cart.coupon &&
|
{cart.coupon &&
|
||||||
<p>{t('app.public.store_cart.checkout_coupon')} <span>-{FormatLib.price(couponAmount())}</span></p>
|
<p>{t('app.public.store_cart.checkout_coupon')} <span>-{FormatLib.price(OrderLib.couponAmount(cart))}</span></p>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<p className='total'>{t('app.public.store_cart.checkout_total')} <span>{FormatLib.price(paidTotal())}</span></p>
|
<p className='total'>{t('app.public.store_cart.checkout_total')} <span>{FormatLib.price(OrderLib.paidTotal(cart))}</span></p>
|
||||||
</div>
|
</div>
|
||||||
<FabButton className='checkout-btn' onClick={checkout} disabled={!cart.user}>
|
<FabButton className='checkout-btn' onClick={checkout} disabled={!cart.user}>
|
||||||
{t('app.public.store_cart.checkout')}
|
{t('app.public.store_cart.checkout')}
|
||||||
|
@ -19,10 +19,10 @@ export const OrderItem: React.FC<OrderItemProps> = ({ order, currentUser }) => {
|
|||||||
/**
|
/**
|
||||||
* Go to order page
|
* Go to order page
|
||||||
*/
|
*/
|
||||||
const showOrder = (ref: string) => {
|
const showOrder = (order: Order) => {
|
||||||
isPrivileged()
|
isPrivileged()
|
||||||
? window.location.href = `/#!/admin/store/o/${ref}`
|
? window.location.href = `/#!/admin/store/orders/${order.id}`
|
||||||
: window.location.href = `/#!/store/o/${ref}`;
|
: window.location.href = `/#!/dashboard/orders/${order.id}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,7 +41,7 @@ export const OrderItem: React.FC<OrderItemProps> = ({ order, currentUser }) => {
|
|||||||
return 'error';
|
return 'error';
|
||||||
case 'canceled':
|
case 'canceled':
|
||||||
return 'canceled';
|
return 'canceled';
|
||||||
case 'pending' || 'under_preparation':
|
case 'in_progress':
|
||||||
return 'pending';
|
return 'pending';
|
||||||
default:
|
default:
|
||||||
return 'normal';
|
return 'normal';
|
||||||
@ -50,24 +50,24 @@ export const OrderItem: React.FC<OrderItemProps> = ({ order, currentUser }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='order-item'>
|
<div className='order-item'>
|
||||||
<p className="ref">order.ref</p>
|
<p className="ref">{order.reference}</p>
|
||||||
<div>
|
<div>
|
||||||
<FabStateLabel status={statusColor('pending')} background>
|
<FabStateLabel status={statusColor(order.state)} background>
|
||||||
order.state
|
{t(`app.shared.store.order_item.state.${order.state}`)}
|
||||||
</FabStateLabel>
|
</FabStateLabel>
|
||||||
</div>
|
</div>
|
||||||
{isPrivileged() &&
|
{isPrivileged() &&
|
||||||
<div className='client'>
|
<div className='client'>
|
||||||
<span>{t('app.shared.store.order_item.client')}</span>
|
<span>{t('app.shared.store.order_item.client')}</span>
|
||||||
<p>order.user.name</p>
|
<p>{order?.user?.name || ''}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<p className="date">order.created_at</p>
|
<p className="date">{FormatLib.date(order.created_at)}</p>
|
||||||
<div className='price'>
|
<div className='price'>
|
||||||
<span>{t('app.shared.store.order_item.total')}</span>
|
<span>{t('app.shared.store.order_item.total')}</span>
|
||||||
<p>{FormatLib.price(order?.total)}</p>
|
<p>{FormatLib.price(order?.total)}</p>
|
||||||
</div>
|
</div>
|
||||||
<FabButton onClick={() => showOrder('orderRef')} icon={<i className="fas fa-eye" />} className="is-black" />
|
<FabButton onClick={() => showOrder(order)} icon={<i className="fas fa-eye" />} className="is-black" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { react2angular } from 'react2angular';
|
import { react2angular } from 'react2angular';
|
||||||
import { Loader } from '../base/loader';
|
import { Loader } from '../base/loader';
|
||||||
@ -6,10 +6,14 @@ import { IApplication } from '../../models/application';
|
|||||||
import { StoreListHeader } from './store-list-header';
|
import { StoreListHeader } from './store-list-header';
|
||||||
import { OrderItem } from './order-item';
|
import { OrderItem } from './order-item';
|
||||||
import { FabPagination } from '../base/fab-pagination';
|
import { FabPagination } from '../base/fab-pagination';
|
||||||
|
import OrderAPI from '../../api/order';
|
||||||
|
import { Order } from '../../models/order';
|
||||||
|
import { User } from '../../models/user';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
interface OrdersDashboardProps {
|
interface OrdersDashboardProps {
|
||||||
|
currentUser: User,
|
||||||
onError: (message: string) => void
|
onError: (message: string) => void
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -21,15 +25,21 @@ type selectOption = { value: number, label: string };
|
|||||||
/**
|
/**
|
||||||
* This component shows a list of all orders from the store for the current user
|
* This component shows a list of all orders from the store for the current user
|
||||||
*/
|
*/
|
||||||
// TODO: delete next eslint disable
|
export const OrdersDashboard: React.FC<OrdersDashboardProps> = ({ currentUser, onError }) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
export const OrdersDashboard: React.FC<OrdersDashboardProps> = ({ onError }) => {
|
|
||||||
const { t } = useTranslation('public');
|
const { t } = useTranslation('public');
|
||||||
|
|
||||||
// TODO: delete next eslint disable
|
const [orders, setOrders] = useState<Array<Order>>([]);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const [pageCount, setPageCount] = useState<number>(0);
|
const [pageCount, setPageCount] = useState<number>(0);
|
||||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||||
|
const [totalCount, setTotalCount] = useState<number>(1);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
OrderAPI.index({}).then(res => {
|
||||||
|
setPageCount(res.total_pages);
|
||||||
|
setTotalCount(res.total_count);
|
||||||
|
setOrders(res.data);
|
||||||
|
}).catch(onError);
|
||||||
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates sorting options to the react-select format
|
* Creates sorting options to the react-select format
|
||||||
@ -44,7 +54,26 @@ export const OrdersDashboard: React.FC<OrdersDashboardProps> = ({ onError }) =>
|
|||||||
* Display option: sorting
|
* Display option: sorting
|
||||||
*/
|
*/
|
||||||
const handleSorting = (option: selectOption) => {
|
const handleSorting = (option: selectOption) => {
|
||||||
console.log('Sort option:', option);
|
OrderAPI.index({ page: 1, sort: option.value ? 'ASC' : 'DESC' }).then(res => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
setOrders(res.data);
|
||||||
|
setPageCount(res.total_pages);
|
||||||
|
setTotalCount(res.total_count);
|
||||||
|
}).catch(onError);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle orders pagination
|
||||||
|
*/
|
||||||
|
const handlePagination = (page: number) => {
|
||||||
|
if (page !== currentPage) {
|
||||||
|
OrderAPI.index({ page }).then(res => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
setOrders(res.data);
|
||||||
|
setPageCount(res.total_pages);
|
||||||
|
setTotalCount(res.total_count);
|
||||||
|
}).catch(onError);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -55,15 +84,17 @@ export const OrdersDashboard: React.FC<OrdersDashboardProps> = ({ onError }) =>
|
|||||||
|
|
||||||
<div className="store-list">
|
<div className="store-list">
|
||||||
<StoreListHeader
|
<StoreListHeader
|
||||||
productsCount={0}
|
productsCount={totalCount}
|
||||||
selectOptions={buildOptions()}
|
selectOptions={buildOptions()}
|
||||||
onSelectOptionsChange={handleSorting}
|
onSelectOptionsChange={handleSorting}
|
||||||
/>
|
/>
|
||||||
<div className="orders-list">
|
<div className="orders-list">
|
||||||
<OrderItem />
|
{orders.map(order => (
|
||||||
|
<OrderItem key={order.id} order={order} currentUser={currentUser} />
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
{pageCount > 1 &&
|
{pageCount > 1 &&
|
||||||
<FabPagination pageCount={pageCount} currentPage={currentPage} selectPage={setCurrentPage} />
|
<FabPagination pageCount={pageCount} currentPage={currentPage} selectPage={handlePagination} />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -78,4 +109,4 @@ const OrdersDashboardWrapper: React.FC<OrdersDashboardProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Application.Components.component('ordersDashboard', react2angular(OrdersDashboardWrapper, ['onError']));
|
Application.Components.component('ordersDashboard', react2angular(OrdersDashboardWrapper, ['onError', 'currentUser']));
|
||||||
|
@ -13,6 +13,8 @@ import { MemberSelect } from '../user/member-select';
|
|||||||
import { User } from '../../models/user';
|
import { User } from '../../models/user';
|
||||||
import { FormInput } from '../form/form-input';
|
import { FormInput } from '../form/form-input';
|
||||||
import { TDateISODate } from '../../typings/date-iso';
|
import { TDateISODate } from '../../typings/date-iso';
|
||||||
|
import OrderAPI from '../../api/order';
|
||||||
|
import { Order } from '../../models/order';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
@ -42,10 +44,17 @@ const Orders: React.FC<OrdersProps> = ({ currentUser, onSuccess, onError }) => {
|
|||||||
|
|
||||||
const { register, getValues } = useForm();
|
const { register, getValues } = useForm();
|
||||||
|
|
||||||
|
const [orders, setOrders] = useState<Array<Order>>([]);
|
||||||
const [filters, setFilters] = useImmer<Filters>(initFilters);
|
const [filters, setFilters] = useImmer<Filters>(initFilters);
|
||||||
const [clearFilters, setClearFilters] = useState<boolean>(false);
|
const [clearFilters, setClearFilters] = useState<boolean>(false);
|
||||||
const [accordion, setAccordion] = useState({});
|
const [accordion, setAccordion] = useState({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
OrderAPI.index({}).then(res => {
|
||||||
|
setOrders(res.data);
|
||||||
|
}).catch(onError);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
applyFilters();
|
applyFilters();
|
||||||
setClearFilters(false);
|
setClearFilters(false);
|
||||||
@ -228,7 +237,9 @@ const Orders: React.FC<OrdersProps> = ({ currentUser, onSuccess, onError }) => {
|
|||||||
onSelectOptionsChange={handleSorting}
|
onSelectOptionsChange={handleSorting}
|
||||||
/>
|
/>
|
||||||
<div className="orders-list">
|
<div className="orders-list">
|
||||||
<OrderItem currentUser={currentUser} />
|
{orders.map(order => (
|
||||||
|
<OrderItem key={order.id} order={order} currentUser={currentUser} />
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { IApplication } from '../../models/application';
|
import { IApplication } from '../../models/application';
|
||||||
import { User } from '../../models/user';
|
import { User } from '../../models/user';
|
||||||
@ -7,11 +7,15 @@ import { Loader } from '../base/loader';
|
|||||||
import noImage from '../../../../images/no_image.png';
|
import noImage from '../../../../images/no_image.png';
|
||||||
import { FabStateLabel } from '../base/fab-state-label';
|
import { FabStateLabel } from '../base/fab-state-label';
|
||||||
import Select from 'react-select';
|
import Select from 'react-select';
|
||||||
|
import OrderAPI from '../../api/order';
|
||||||
|
import { Order } from '../../models/order';
|
||||||
|
import FormatLib from '../../lib/format';
|
||||||
|
import OrderLib from '../../lib/order';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
interface ShowOrderProps {
|
interface ShowOrderProps {
|
||||||
orderRef: string,
|
orderId: string,
|
||||||
currentUser?: User,
|
currentUser?: User,
|
||||||
onError: (message: string) => void,
|
onError: (message: string) => void,
|
||||||
onSuccess: (message: string) => void
|
onSuccess: (message: string) => void
|
||||||
@ -27,9 +31,17 @@ type selectOption = { value: number, label: string };
|
|||||||
*/
|
*/
|
||||||
// TODO: delete next eslint disable
|
// TODO: delete next eslint disable
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
export const ShowOrder: React.FC<ShowOrderProps> = ({ orderRef, currentUser, onError, onSuccess }) => {
|
export const ShowOrder: React.FC<ShowOrderProps> = ({ orderId, currentUser, onError, onSuccess }) => {
|
||||||
const { t } = useTranslation('shared');
|
const { t } = useTranslation('shared');
|
||||||
|
|
||||||
|
const [order, setOrder] = useState<Order>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
OrderAPI.get(orderId).then(data => {
|
||||||
|
setOrder(data);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the current operator has administrative rights or is a normal member
|
* Check if the current operator has administrative rights or is a normal member
|
||||||
*/
|
*/
|
||||||
@ -42,14 +54,14 @@ export const ShowOrder: React.FC<ShowOrderProps> = ({ orderRef, currentUser, onE
|
|||||||
*/
|
*/
|
||||||
const buildOptions = (): Array<selectOption> => {
|
const buildOptions = (): Array<selectOption> => {
|
||||||
return [
|
return [
|
||||||
{ value: 0, label: t('app.shared.store.show_order.status.error') },
|
{ value: 0, label: t('app.shared.store.show_order.state.error') },
|
||||||
{ value: 1, label: t('app.shared.store.show_order.status.canceled') },
|
{ value: 1, label: t('app.shared.store.show_order.state.canceled') },
|
||||||
{ value: 2, label: t('app.shared.store.show_order.status.pending') },
|
{ value: 2, label: t('app.shared.store.show_order.state.pending') },
|
||||||
{ value: 3, label: t('app.shared.store.show_order.status.under_preparation') },
|
{ value: 3, label: t('app.shared.store.show_order.state.under_preparation') },
|
||||||
{ value: 4, label: t('app.shared.store.show_order.status.paid') },
|
{ value: 4, label: t('app.shared.store.show_order.state.paid') },
|
||||||
{ value: 5, label: t('app.shared.store.show_order.status.ready') },
|
{ value: 5, label: t('app.shared.store.show_order.state.ready') },
|
||||||
{ value: 6, label: t('app.shared.store.show_order.status.collected') },
|
{ value: 6, label: t('app.shared.store.show_order.state.collected') },
|
||||||
{ value: 7, label: t('app.shared.store.show_order.status.refunded') }
|
{ value: 7, label: t('app.shared.store.show_order.state.refunded') }
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,17 +93,21 @@ export const ShowOrder: React.FC<ShowOrderProps> = ({ orderRef, currentUser, onE
|
|||||||
return 'error';
|
return 'error';
|
||||||
case 'canceled':
|
case 'canceled':
|
||||||
return 'canceled';
|
return 'canceled';
|
||||||
case 'pending' || 'under_preparation':
|
case 'in_progress':
|
||||||
return 'pending';
|
return 'pending';
|
||||||
default:
|
default:
|
||||||
return 'normal';
|
return 'normal';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!order) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='show-order'>
|
<div className='show-order'>
|
||||||
<header>
|
<header>
|
||||||
<h2>[order.ref]</h2>
|
<h2>[{order.reference}]</h2>
|
||||||
<div className="grpBtn">
|
<div className="grpBtn">
|
||||||
{isPrivileged() &&
|
{isPrivileged() &&
|
||||||
<Select
|
<Select
|
||||||
@ -100,19 +116,21 @@ export const ShowOrder: React.FC<ShowOrderProps> = ({ orderRef, currentUser, onE
|
|||||||
styles={customStyles}
|
styles={customStyles}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
<a href={''}
|
{order?.invoice_id && (
|
||||||
|
<a href={`/api/invoices/${order?.invoice_id}/download`}
|
||||||
target='_blank'
|
target='_blank'
|
||||||
className='fab-button is-black'
|
className='fab-button is-black'
|
||||||
rel='noreferrer'>
|
rel='noreferrer'>
|
||||||
{t('app.shared.store.show_order.see_invoice')}
|
{t('app.shared.store.show_order.see_invoice')}
|
||||||
</a>
|
</a>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="client-info">
|
<div className="client-info">
|
||||||
<label>{t('app.shared.store.show_order.tracking')}</label>
|
<label>{t('app.shared.store.show_order.tracking')}</label>
|
||||||
<div className="content">
|
<div className="content">
|
||||||
{isPrivileged() &&
|
{isPrivileged() && order.user &&
|
||||||
<div className='group'>
|
<div className='group'>
|
||||||
<span>{t('app.shared.store.show_order.client')}</span>
|
<span>{t('app.shared.store.show_order.client')}</span>
|
||||||
<p>order.user.name</p>
|
<p>order.user.name</p>
|
||||||
@ -120,14 +138,14 @@ export const ShowOrder: React.FC<ShowOrderProps> = ({ orderRef, currentUser, onE
|
|||||||
}
|
}
|
||||||
<div className='group'>
|
<div className='group'>
|
||||||
<span>{t('app.shared.store.show_order.created_at')}</span>
|
<span>{t('app.shared.store.show_order.created_at')}</span>
|
||||||
<p>order.created_at</p>
|
<p>{FormatLib.date(order.created_at)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='group'>
|
<div className='group'>
|
||||||
<span>{t('app.shared.store.show_order.last_update')}</span>
|
<span>{t('app.shared.store.show_order.last_update')}</span>
|
||||||
<p>order.???</p>
|
<p>{FormatLib.date(order.updated_at)}</p>
|
||||||
</div>
|
</div>
|
||||||
<FabStateLabel status={statusColor('error')} background>
|
<FabStateLabel status={statusColor(order.state)} background>
|
||||||
order.state
|
{t(`app.shared.store.show_order.state.${order.state}`)}
|
||||||
</FabStateLabel>
|
</FabStateLabel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -135,29 +153,30 @@ export const ShowOrder: React.FC<ShowOrderProps> = ({ orderRef, currentUser, onE
|
|||||||
<div className="cart">
|
<div className="cart">
|
||||||
<label>{t('app.shared.store.show_order.cart')}</label>
|
<label>{t('app.shared.store.show_order.cart')}</label>
|
||||||
<div>
|
<div>
|
||||||
{/* loop sur les articles du panier */}
|
{order.order_items_attributes.map(item => (
|
||||||
<article className='store-cart-list-item'>
|
<article className='store-cart-list-item' key={item.id}>
|
||||||
<div className='picture'>
|
<div className='picture'>
|
||||||
<img alt=''src={noImage} />
|
<img alt=''src={item.orderable_main_image_url || noImage} />
|
||||||
</div>
|
</div>
|
||||||
<div className="ref">
|
<div className="ref">
|
||||||
<span>{t('app.shared.store.show_order.reference_short')} orderable_id?</span>
|
<span>{t('app.shared.store.show_order.reference_short')}</span>
|
||||||
<p>o.orderable_name</p>
|
<p>{item.orderable_name}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
<div className='price'>
|
<div className='price'>
|
||||||
<p>o.amount</p>
|
<p>{FormatLib.price(item.amount)}</p>
|
||||||
<span>/ {t('app.shared.store.show_order.unit')}</span>
|
<span>/ {t('app.shared.store.show_order.unit')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span className="count">o.quantity</span>
|
<span className="count">{item.quantity}</span>
|
||||||
|
|
||||||
<div className='total'>
|
<div className='total'>
|
||||||
<span>{t('app.shared.store.show_order.item_total')}</span>
|
<span>{t('app.shared.store.show_order.item_total')}</span>
|
||||||
<p>o.quantity * o.amount</p>
|
<p>{FormatLib.price(OrderLib.itemAmount(item))}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -168,10 +187,14 @@ export const ShowOrder: React.FC<ShowOrderProps> = ({ orderRef, currentUser, onE
|
|||||||
</div>
|
</div>
|
||||||
<div className="amount">
|
<div className="amount">
|
||||||
<label>{t('app.shared.store.show_order.amount')}</label>
|
<label>{t('app.shared.store.show_order.amount')}</label>
|
||||||
<p>{t('app.shared.store.show_order.products_total')}<span>order.amount</span></p>
|
<p>{t('app.shared.store.show_order.products_total')}<span>{FormatLib.price(OrderLib.totalBeforeOfferedAmount(order))}</span></p>
|
||||||
<p className='gift'>{t('app.shared.store.show_order.gift_total')}<span>-order.amount</span></p>
|
{OrderLib.hasOfferedItem(order) &&
|
||||||
<p>{t('app.shared.store.show_order.coupon')}<span>order.amount</span></p>
|
<p className='gift'>{t('app.shared.store.show_order.gift_total')}<span>-{FormatLib.price(OrderLib.offeredAmount(order))}</span></p>
|
||||||
<p className='total'>{t('app.shared.store.show_order.cart_total')} <span>order.total</span></p>
|
}
|
||||||
|
{order.coupon &&
|
||||||
|
<p>{t('app.shared.store.show_order.coupon')}<span>-{FormatLib.price(OrderLib.couponAmount(order))}</span></p>
|
||||||
|
}
|
||||||
|
<p className='total'>{t('app.shared.store.show_order.cart_total')} <span>{FormatLib.price(OrderLib.paidTotal(order))}</span></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -186,4 +209,4 @@ const ShowOrderWrapper: React.FC<ShowOrderProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Application.Components.component('showOrder', react2angular(ShowOrderWrapper, ['orderRef', 'currentUser', 'onError', 'onSuccess']));
|
Application.Components.component('showOrder', react2angular(ShowOrderWrapper, ['orderId', 'currentUser', 'onError', 'onSuccess']));
|
||||||
|
@ -9,7 +9,7 @@ Application.Controllers.controller('ShowOrdersController', ['$rootScope', '$scop
|
|||||||
/* PRIVATE SCOPE */
|
/* PRIVATE SCOPE */
|
||||||
|
|
||||||
/* PUBLIC SCOPE */
|
/* PUBLIC SCOPE */
|
||||||
$scope.orderToken = $transition$.params().token;
|
$scope.orderId = $transition$.params().id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback triggered in case of error
|
* Callback triggered in case of error
|
||||||
|
50
app/frontend/src/javascript/lib/order.ts
Normal file
50
app/frontend/src/javascript/lib/order.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { computePriceWithCoupon } from './coupon';
|
||||||
|
import { Order } from '../models/order';
|
||||||
|
|
||||||
|
export default class OrderLib {
|
||||||
|
/**
|
||||||
|
* Get the order item total
|
||||||
|
*/
|
||||||
|
static itemAmount = (item): number => {
|
||||||
|
return item.quantity * Math.trunc(item.amount * 100) / 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return true if order has offered item
|
||||||
|
*/
|
||||||
|
static hasOfferedItem = (order: Order): boolean => {
|
||||||
|
return order.order_items_attributes
|
||||||
|
.filter(i => i.is_offered).length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the offered item total
|
||||||
|
*/
|
||||||
|
static offeredAmount = (order: Order): number => {
|
||||||
|
return order.order_items_attributes
|
||||||
|
.filter(i => i.is_offered)
|
||||||
|
.map(i => Math.trunc(i.amount * 100) * i.quantity)
|
||||||
|
.reduce((acc, curr) => acc + curr, 0) / 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the total amount before offered amount
|
||||||
|
*/
|
||||||
|
static totalBeforeOfferedAmount = (order: Order): number => {
|
||||||
|
return (Math.trunc(order.total * 100) + Math.trunc(this.offeredAmount(order) * 100)) / 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the coupon amount
|
||||||
|
*/
|
||||||
|
static couponAmount = (order: Order): number => {
|
||||||
|
return (Math.trunc(order.total * 100) - Math.trunc(computePriceWithCoupon(order.total, order.coupon) * 100)) / 100.00;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the paid total amount
|
||||||
|
*/
|
||||||
|
static paidTotal = (order: Order): number => {
|
||||||
|
return computePriceWithCoupon(order.total, order.coupon);
|
||||||
|
};
|
||||||
|
}
|
@ -20,11 +20,14 @@ export interface Order {
|
|||||||
total?: number,
|
total?: number,
|
||||||
coupon?: Coupon,
|
coupon?: Coupon,
|
||||||
created_at?: TDateISO,
|
created_at?: TDateISO,
|
||||||
|
updated_at?: TDateISO,
|
||||||
|
invoice_id?: number,
|
||||||
order_items_attributes: Array<{
|
order_items_attributes: Array<{
|
||||||
id: number,
|
id: number,
|
||||||
orderable_type: string,
|
orderable_type: string,
|
||||||
orderable_id: number,
|
orderable_id: number,
|
||||||
orderable_name: string,
|
orderable_name: string,
|
||||||
|
orderable_main_image_url?: string,
|
||||||
quantity: number,
|
quantity: number,
|
||||||
amount: number,
|
amount: number,
|
||||||
is_offered: boolean
|
is_offered: boolean
|
||||||
@ -35,3 +38,17 @@ export interface OrderPayment {
|
|||||||
order: Order,
|
order: Order,
|
||||||
payment?: PaymentConfirmation|CreateTokenResponse
|
payment?: PaymentConfirmation|CreateTokenResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OrderIndex {
|
||||||
|
page: number,
|
||||||
|
total_pages: number,
|
||||||
|
page_size: number,
|
||||||
|
total_count: number,
|
||||||
|
data: Array<Order>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrderIndexFilter {
|
||||||
|
user_id?: number,
|
||||||
|
page?: number,
|
||||||
|
sort?: 'DESC'|'ASC'
|
||||||
|
}
|
||||||
|
@ -236,6 +236,15 @@ angular.module('application.router', ['ui.router'])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.state('app.logged.dashboard.order_show', {
|
||||||
|
url: '/orders/:id',
|
||||||
|
views: {
|
||||||
|
'main@': {
|
||||||
|
templateUrl: '/orders/show.html',
|
||||||
|
controller: 'ShowOrdersController'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
.state('app.logged.dashboard.wallet', {
|
.state('app.logged.dashboard.wallet', {
|
||||||
url: '/wallet',
|
url: '/wallet',
|
||||||
abstract: !Fablab.walletModule,
|
abstract: !Fablab.walletModule,
|
||||||
@ -631,17 +640,6 @@ angular.module('application.router', ['ui.router'])
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// show order
|
|
||||||
.state('app.public.order_show', {
|
|
||||||
url: '/store/o/:token',
|
|
||||||
views: {
|
|
||||||
'main@': {
|
|
||||||
templateUrl: '/orders/show.html',
|
|
||||||
controller: 'ShowOrdersController'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// cart
|
// cart
|
||||||
.state('app.public.store_cart', {
|
.state('app.public.store_cart', {
|
||||||
url: '/store/cart',
|
url: '/store/cart',
|
||||||
@ -926,7 +924,7 @@ angular.module('application.router', ['ui.router'])
|
|||||||
|
|
||||||
// show order
|
// show order
|
||||||
.state('app.admin.order_show', {
|
.state('app.admin.order_show', {
|
||||||
url: '/admin/store/o/:token',
|
url: '/admin/store/orders/:id',
|
||||||
views: {
|
views: {
|
||||||
'main@': {
|
'main@': {
|
||||||
templateUrl: '/admin/orders/show.html',
|
templateUrl: '/admin/orders/show.html',
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<orders-dashboard on-error="onError" />
|
<orders-dashboard current-user="currentUser" on-error="onError" />
|
||||||
</div>
|
</div>
|
@ -12,6 +12,6 @@
|
|||||||
<span translate>{{ 'app.shared.store.show_order.back_to_list' }}</span>
|
<span translate>{{ 'app.shared.store.show_order.back_to_list' }}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<show-order current-user="currentUser" order-token="orderToken" on-error="onError" on-success="onSuccess" />
|
<show-order current-user="currentUser" order-id="orderId" on-error="onError" on-success="onSuccess" />
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
@ -23,4 +23,8 @@ class Product < ApplicationRecord
|
|||||||
validates :amount, numericality: { greater_than: 0, allow_nil: true }
|
validates :amount, numericality: { greater_than: 0, allow_nil: true }
|
||||||
|
|
||||||
scope :active, -> { where(is_active: true) }
|
scope :active, -> { where(is_active: true) }
|
||||||
|
|
||||||
|
def main_image
|
||||||
|
product_images.find_by(is_main: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
16
app/policies/order_policy.rb
Normal file
16
app/policies/order_policy.rb
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Check the access policies for API::OrdersController
|
||||||
|
class OrderPolicy < ApplicationPolicy
|
||||||
|
def show?
|
||||||
|
user.privileged? || (record.statistic_profile_id == user.statistic_profile.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update?
|
||||||
|
user.privileged?
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy?
|
||||||
|
user.privileged?
|
||||||
|
end
|
||||||
|
end
|
@ -2,6 +2,32 @@
|
|||||||
|
|
||||||
# Provides methods for Order
|
# Provides methods for Order
|
||||||
class Orders::OrderService
|
class Orders::OrderService
|
||||||
|
ORDERS_PER_PAGE = 20
|
||||||
|
|
||||||
|
def self.list(filters, current_user)
|
||||||
|
orders = Order.where(nil)
|
||||||
|
if filters[:user_id]
|
||||||
|
statistic_profile_id = current_user.statistic_profile.id
|
||||||
|
if (current_user.member? && current_user.id == filters[:user_id].to_i) || current_user.privileged?
|
||||||
|
user = User.find(filters[:user_id])
|
||||||
|
statistic_profile_id = user.statistic_profile.id
|
||||||
|
end
|
||||||
|
orders = orders.where(statistic_profile_id: statistic_profile_id)
|
||||||
|
elsif current_user.member?
|
||||||
|
orders = orders.where(statistic_profile_id: current_user.statistic_profile.id)
|
||||||
|
end
|
||||||
|
orders = orders.where.not(state: 'cart') if current_user.member?
|
||||||
|
orders = orders.order(created_at: filters[:page].present? ? filters[:sort] : 'DESC')
|
||||||
|
orders = orders.page(filters[:page]).per(ORDERS_PER_PAGE) if filters[:page].present?
|
||||||
|
{
|
||||||
|
data: orders,
|
||||||
|
page: filters[:page] || 1,
|
||||||
|
total_pages: orders.page(1).per(ORDERS_PER_PAGE).total_pages,
|
||||||
|
page_size: ORDERS_PER_PAGE,
|
||||||
|
total_count: orders.count
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def in_stock?(order, stock_type = 'external')
|
def in_stock?(order, stock_type = 'external')
|
||||||
order.order_items.each do |item|
|
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
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
json.extract! order, :id, :token, :statistic_profile_id, :operator_profile_id, :reference, :state, :created_at
|
json.extract! order, :id, :token, :statistic_profile_id, :operator_profile_id, :reference, :state, :created_at, :updated_at, :invoice_id,
|
||||||
|
:payment_method
|
||||||
json.total order.total / 100.0 if order.total.present?
|
json.total order.total / 100.0 if order.total.present?
|
||||||
|
if order.coupon_id
|
||||||
|
json.coupon do
|
||||||
|
json.extract! order.coupon, :id, :code, :type, :percent_off, :validity_per_user
|
||||||
|
json.amount_off order.coupon.amount_off / 100.00 unless order.coupon.amount_off.nil?
|
||||||
|
end
|
||||||
|
end
|
||||||
if order&.statistic_profile&.user
|
if order&.statistic_profile&.user
|
||||||
json.user do
|
json.user do
|
||||||
json.id order.statistic_profile.user.id
|
json.id order.statistic_profile.user.id
|
||||||
@ -15,6 +22,7 @@ json.order_items_attributes order.order_items.order(created_at: :asc) do |item|
|
|||||||
json.orderable_type item.orderable_type
|
json.orderable_type item.orderable_type
|
||||||
json.orderable_id item.orderable_id
|
json.orderable_id item.orderable_id
|
||||||
json.orderable_name item.orderable.name
|
json.orderable_name item.orderable.name
|
||||||
|
json.orderable_main_image_url item.orderable.main_image&.attachment_url
|
||||||
json.quantity item.quantity
|
json.quantity item.quantity
|
||||||
json.amount item.amount / 100.0
|
json.amount item.amount / 100.0
|
||||||
json.is_offered item.is_offered
|
json.is_offered item.is_offered
|
||||||
|
17
app/views/api/orders/index.json.jbuilder
Normal file
17
app/views/api/orders/index.json.jbuilder
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
json.page @result[:page]
|
||||||
|
json.total_pages @result[:total_pages]
|
||||||
|
json.page_size @result[:page_size]
|
||||||
|
json.total_count @result[:total_count]
|
||||||
|
json.data @result[:data] do |order|
|
||||||
|
json.extract! order, :id, :statistic_profile_id, :reference, :state, :created_at
|
||||||
|
json.total order.total / 100.0 if order.total.present?
|
||||||
|
if order&.statistic_profile&.user
|
||||||
|
json.user do
|
||||||
|
json.id order.statistic_profile.user.id
|
||||||
|
json.role order.statistic_profile.user.roles.first.name
|
||||||
|
json.name order.statistic_profile.user.profile.full_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
3
app/views/api/orders/update.json.jbuilder
Normal file
3
app/views/api/orders/update.json.jbuilder
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
json.partial! 'api/orders/order', order: @order
|
@ -23,6 +23,7 @@ fr:
|
|||||||
my_invoices: "Mes factures"
|
my_invoices: "Mes factures"
|
||||||
my_payment_schedules: "Mes échéanciers"
|
my_payment_schedules: "Mes échéanciers"
|
||||||
my_wallet: "Mon porte-monnaie"
|
my_wallet: "Mon porte-monnaie"
|
||||||
|
my_orders: "Mes commandes"
|
||||||
#contextual help
|
#contextual help
|
||||||
help: "Aide"
|
help: "Aide"
|
||||||
#login/logout
|
#login/logout
|
||||||
|
@ -564,6 +564,9 @@ en:
|
|||||||
order_item:
|
order_item:
|
||||||
total: "Total"
|
total: "Total"
|
||||||
client: "Client"
|
client: "Client"
|
||||||
|
state:
|
||||||
|
cart: 'Cart'
|
||||||
|
in_progress: 'In progress'
|
||||||
show_order:
|
show_order:
|
||||||
back_to_list: "Back to list"
|
back_to_list: "Back to list"
|
||||||
see_invoice: "See invoice"
|
see_invoice: "See invoice"
|
||||||
@ -581,7 +584,9 @@ en:
|
|||||||
gift_total: "Discount total"
|
gift_total: "Discount total"
|
||||||
coupon: "Coupon"
|
coupon: "Coupon"
|
||||||
cart_total: "Cart total"
|
cart_total: "Cart total"
|
||||||
status:
|
state:
|
||||||
|
cart: 'Cart'
|
||||||
|
in_progress: 'In progress'
|
||||||
error: "Payment error"
|
error: "Payment error"
|
||||||
canceled: "Canceled"
|
canceled: "Canceled"
|
||||||
pending: "Pending payment"
|
pending: "Pending payment"
|
||||||
|
@ -165,6 +165,7 @@ Rails.application.routes.draw do
|
|||||||
post 'payment', on: :collection
|
post 'payment', on: :collection
|
||||||
post 'confirm_payment', on: :collection
|
post 'confirm_payment', on: :collection
|
||||||
end
|
end
|
||||||
|
resources :orders, except: %i[create]
|
||||||
|
|
||||||
# for admin
|
# for admin
|
||||||
resources :trainings do
|
resources :trainings do
|
||||||
|
Loading…
x
Reference in New Issue
Block a user