mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-18 07:52:23 +01:00
stock
This commit is contained in:
parent
f21a68593a
commit
3a0248ed98
@ -242,7 +242,7 @@ export const ProductForm: React.FC<ProductFormProps> = ({ product, title, onSucc
|
||||
<p className={stockTab ? 'is-active' : ''} onClick={() => setStockTab(true)}>{t('app.admin.store.product_form.stock_management')}</p>
|
||||
</div>
|
||||
{stockTab
|
||||
? <ProductStockForm product={product} control={control} onError={onError} onSuccess={onSuccess} />
|
||||
? <ProductStockForm product={product} register={register} control={control} id="stock" onError={onError} onSuccess={onSuccess} />
|
||||
: <section>
|
||||
<div className="subgrid">
|
||||
<FormInput id="name"
|
||||
@ -277,13 +277,12 @@ export const ProductForm: React.FC<ProductFormProps> = ({ product, title, onSucc
|
||||
|
||||
<div className="price-data">
|
||||
<div className="header-switch">
|
||||
<h4 className='span-7'>{t('app.admin.store.product_form.price_and_rule_of_selling_product')}</h4>
|
||||
<h4>{t('app.admin.store.product_form.price_and_rule_of_selling_product')}</h4>
|
||||
<FormSwitch control={control}
|
||||
id="is_active_price"
|
||||
label={t('app.admin.store.product_form.is_active_price')}
|
||||
defaultValue={isActivePrice}
|
||||
onChange={toggleIsActivePrice}
|
||||
className='span-3' />
|
||||
onChange={toggleIsActivePrice} />
|
||||
</div>
|
||||
{isActivePrice && <div className="price-data-content">
|
||||
<FormInput id="amount"
|
||||
|
@ -1,44 +1,208 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Product } from '../../models/product';
|
||||
import { UseFormRegister } from 'react-hook-form';
|
||||
import { Control, FormState } from 'react-hook-form/dist/types/form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Control } from 'react-hook-form';
|
||||
import { HtmlTranslate } from '../base/html-translate';
|
||||
import { FormSwitch } from '../form/form-switch';
|
||||
import { FormInput } from '../form/form-input';
|
||||
import Select from 'react-select';
|
||||
import { FabAlert } from '../base/fab-alert';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { PencilSimple } from 'phosphor-react';
|
||||
import { FabModal, ModalSize } from '../base/fab-modal';
|
||||
import { ProductStockModal } from './product-stock-modal';
|
||||
|
||||
interface ProductStockFormProps {
|
||||
interface ProductStockFormProps<TFieldValues, TContext extends object> {
|
||||
product: Product,
|
||||
control: Control,
|
||||
register: UseFormRegister<TFieldValues>,
|
||||
control: Control<TFieldValues, TContext>,
|
||||
formState: FormState<TFieldValues>,
|
||||
onSuccess: (product: Product) => void,
|
||||
onError: (message: string) => void,
|
||||
}
|
||||
|
||||
/**
|
||||
* Form tab to manage product's stock
|
||||
* Form tab to manage a product's stock
|
||||
*/
|
||||
export const ProductStockForm: React.FC<ProductStockFormProps> = ({ product, control, onError, onSuccess }) => {
|
||||
export const ProductStockForm = <TFieldValues, TContext extends object> ({ product, register, control, formState, onError, onSuccess }: ProductStockFormProps<TFieldValues, TContext>) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const [activeThreshold, setActiveThreshold] = useState<boolean>(false);
|
||||
// is the modal open?
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
|
||||
// Styles the React-select component
|
||||
const customStyles = {
|
||||
control: base => ({
|
||||
...base,
|
||||
width: '20ch',
|
||||
border: 'none',
|
||||
backgroundColor: 'transparent'
|
||||
}),
|
||||
indicatorSeparator: () => ({
|
||||
display: 'none'
|
||||
})
|
||||
};
|
||||
|
||||
type selectOption = { value: number, label: string };
|
||||
/**
|
||||
* Creates sorting options to the react-select format
|
||||
*/
|
||||
const buildEventsOptions = (): Array<selectOption> => {
|
||||
return [
|
||||
{ value: 0, label: t('app.admin.store.product_stock_form.events.inward_stock') },
|
||||
{ value: 1, label: t('app.admin.store.product_stock_form.events.returned') },
|
||||
{ value: 2, label: t('app.admin.store.product_stock_form.events.canceled') },
|
||||
{ value: 3, label: t('app.admin.store.product_stock_form.events.sold') },
|
||||
{ value: 4, label: t('app.admin.store.product_stock_form.events.missing') },
|
||||
{ value: 5, label: t('app.admin.store.product_stock_form.events.damaged') }
|
||||
];
|
||||
};
|
||||
/**
|
||||
* Creates sorting options to the react-select format
|
||||
*/
|
||||
const buildStocksOptions = (): Array<selectOption> => {
|
||||
return [
|
||||
{ value: 0, label: t('app.admin.store.product_stock_form.internal') },
|
||||
{ value: 1, label: t('app.admin.store.product_stock_form.external') },
|
||||
{ value: 2, label: t('app.admin.store.product_stock_form.all') }
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* On events option change
|
||||
*/
|
||||
const eventsOptionsChange = (evt: selectOption) => {
|
||||
console.log('Event option:', evt);
|
||||
};
|
||||
/**
|
||||
* On stocks option change
|
||||
*/
|
||||
const stocksOptionsChange = (evt: selectOption) => {
|
||||
console.log('Stocks option:', evt);
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle stock threshold
|
||||
*/
|
||||
const toggleStockThreshold = (checked: boolean) => {
|
||||
console.log('Stock threshold:', checked);
|
||||
setActiveThreshold(checked);
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens/closes the product category modal
|
||||
*/
|
||||
const toggleModal = (): void => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle stock threshold alert
|
||||
*/
|
||||
const toggleStockThresholdAlert = (checked: boolean) => {
|
||||
console.log('Low stock notification:', checked);
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section className='product-stock-form'>
|
||||
<h4>Stock à jour <span>00/00/0000 - 00H30</span></h4>
|
||||
<div></div>
|
||||
<div className="stock-item">
|
||||
<p className='title'>Product name</p>
|
||||
<div className="group">
|
||||
<span>{t('app.admin.store.product_stock_form.internal')}</span>
|
||||
<p>00</p>
|
||||
</div>
|
||||
<div className="group">
|
||||
<span>{t('app.admin.store.product_stock_form.external')}</span>
|
||||
<p>000</p>
|
||||
</div>
|
||||
<FabButton onClick={toggleModal} icon={<PencilSimple size={20} weight="fill" />} className="is-black">Modifier</FabButton>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<div className="header-switch">
|
||||
<h4 className='span-7'>{t('app.admin.store.product_stock_form.low_stock_threshold')}</h4>
|
||||
<FormSwitch control={control}
|
||||
id="is_active_threshold"
|
||||
label={t('app.admin.store.product_stock_form.toggle_stock_threshold')}
|
||||
defaultValue={false}
|
||||
onChange={toggleStockThreshold}
|
||||
className='span-3' />
|
||||
<div className="threshold-data">
|
||||
<div className="header-switch">
|
||||
<h4>{t('app.admin.store.product_stock_form.low_stock_threshold')}</h4>
|
||||
<FormSwitch control={control}
|
||||
id="is_active_threshold"
|
||||
label={t('app.admin.store.product_stock_form.stock_threshold_toggle')}
|
||||
defaultValue={activeThreshold}
|
||||
onChange={toggleStockThreshold} />
|
||||
</div>
|
||||
<FabAlert level="warning">
|
||||
<HtmlTranslate trKey="app.admin.store.product_stock_form.stock_threshold_information" />
|
||||
</FabAlert>
|
||||
{activeThreshold && <>
|
||||
<span className='stock-label'>{t('app.admin.store.product_stock_form.low_stock')}</span>
|
||||
<div className="threshold-data-content">
|
||||
<FormInput id="threshold"
|
||||
type="number"
|
||||
register={register}
|
||||
rules={{ required: true, min: 1 }}
|
||||
step={1}
|
||||
formState={formState}
|
||||
label={t('app.admin.store.product_stock_form.threshold_level')} />
|
||||
<FormSwitch control={control}
|
||||
id="threshold_alert"
|
||||
formState={formState}
|
||||
label={t('app.admin.store.product_stock_form.threshold_alert')}
|
||||
defaultValue={activeThreshold}
|
||||
onChange={toggleStockThresholdAlert} />
|
||||
</div>
|
||||
</>}
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<div className="store-list">
|
||||
<h4>{t('app.admin.store.product_stock_form.events_history')}</h4>
|
||||
<div className="store-list-header">
|
||||
<div className='sort-events'>
|
||||
<p>{t('app.admin.store.product_stock_form.event_type')}</p>
|
||||
<Select
|
||||
options={buildEventsOptions()}
|
||||
onChange={evt => eventsOptionsChange(evt)}
|
||||
styles={customStyles}
|
||||
/>
|
||||
</div>
|
||||
<div className='sort-stocks'>
|
||||
<p>{t('app.admin.store.product_stock_form.stocks')}</p>
|
||||
<Select
|
||||
options={buildStocksOptions()}
|
||||
onChange={evt => stocksOptionsChange(evt)}
|
||||
styles={customStyles}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="stock-history">
|
||||
<div className="stock-item">
|
||||
<p className='title'>Product name</p>
|
||||
<p>00/00/0000</p>
|
||||
<div className="group">
|
||||
<span>[stock type]</span>
|
||||
<p>00</p>
|
||||
</div>
|
||||
<div className="group">
|
||||
<span>{t('app.admin.store.product_stock_form.event_type')}</span>
|
||||
<p>[event type]</p>
|
||||
</div>
|
||||
<div className="group">
|
||||
<span>{t('app.admin.store.product_stock_form.stock_level')}</span>
|
||||
<p>000</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FabModal title={t('app.admin.store.product_stock_form.modal_title')}
|
||||
className="fab-modal-lg"
|
||||
width={ModalSize.large}
|
||||
isOpen={isOpen}
|
||||
toggleModal={toggleModal}
|
||||
closeButton>
|
||||
<ProductStockModal product={product} register={register} control={control} id="stock-modal" onError={onError} onSuccess={onSuccess} />
|
||||
</FabModal>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,96 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Product } from '../../models/product';
|
||||
import { UseFormRegister } from 'react-hook-form';
|
||||
import { Control, FormState } from 'react-hook-form/dist/types/form';
|
||||
import { FormSelect } from '../form/form-select';
|
||||
import { FormInput } from '../form/form-input';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
|
||||
type selectOption = { value: number, label: string };
|
||||
|
||||
interface ProductStockModalProps<TFieldValues, TContext extends object> {
|
||||
product: Product,
|
||||
register: UseFormRegister<TFieldValues>,
|
||||
control: Control<TFieldValues, TContext>,
|
||||
formState: FormState<TFieldValues>,
|
||||
onSuccess: (product: Product) => void,
|
||||
onError: (message: string) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Form to manage a product's stock movement and quantity
|
||||
*/
|
||||
export const ProductStockModal = <TFieldValues, TContext extends object> ({ product, register, control, formState, onError, onSuccess }: ProductStockModalProps<TFieldValues, TContext>) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const [movement, setMovement] = useState<'in' | 'out'>('in');
|
||||
|
||||
/**
|
||||
* Toggle between adding or removing product from stock
|
||||
*/
|
||||
const toggleMovementType = (evt: React.MouseEvent<HTMLButtonElement, MouseEvent>, type: 'in' | 'out') => {
|
||||
evt.preventDefault();
|
||||
setMovement(type);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates sorting options to the react-select format
|
||||
*/
|
||||
const buildEventsOptions = (): Array<selectOption> => {
|
||||
let options = [];
|
||||
movement === 'in'
|
||||
? options = [
|
||||
{ value: 0, label: t('app.admin.store.product_stock_modal.events.inward_stock') },
|
||||
{ value: 1, label: t('app.admin.store.product_stock_modal.events.returned') },
|
||||
{ value: 2, label: t('app.admin.store.product_stock_modal.events.canceled') }
|
||||
]
|
||||
: options = [
|
||||
{ value: 0, label: t('app.admin.store.product_stock_modal.events.sold') },
|
||||
{ value: 1, label: t('app.admin.store.product_stock_modal.events.missing') },
|
||||
{ value: 2, label: t('app.admin.store.product_stock_modal.events.damaged') }
|
||||
];
|
||||
return options;
|
||||
};
|
||||
/**
|
||||
* Creates sorting options to the react-select format
|
||||
*/
|
||||
const buildStocksOptions = (): Array<selectOption> => {
|
||||
return [
|
||||
{ value: 0, label: t('app.admin.store.product_stock_modal.internal') },
|
||||
{ value: 1, label: t('app.admin.store.product_stock_modal.external') }
|
||||
];
|
||||
};
|
||||
|
||||
return (
|
||||
<form className='product-stock-modal'>
|
||||
<p className='subtitle'>{t('app.admin.store.product_stock_modal.new_event')}</p>
|
||||
<div className="movement">
|
||||
<button onClick={(evt) => toggleMovementType(evt, 'in')} className={movement === 'in' ? 'is-active' : ''}>
|
||||
{t('app.admin.store.product_stock_modal.addition')}
|
||||
</button>
|
||||
<button onClick={(evt) => toggleMovementType(evt, 'out')} className={movement === 'out' ? 'is-active' : ''}>
|
||||
{t('app.admin.store.product_stock_modal.withdrawal')}
|
||||
</button>
|
||||
</div>
|
||||
<FormSelect options={buildStocksOptions()}
|
||||
control={control}
|
||||
id="updated_stock_type"
|
||||
formState={formState}
|
||||
label={t('app.admin.store.product_stock_modal.stocks')} />
|
||||
<FormInput id="updated_stock_quantity"
|
||||
type="number"
|
||||
register={register}
|
||||
rules={{ required: true, min: 1 }}
|
||||
step={1}
|
||||
formState={formState}
|
||||
label={t('app.admin.store.product_stock_modal.quantity')} />
|
||||
<FormSelect options={buildEventsOptions()}
|
||||
control={control}
|
||||
id="updated_stock_event"
|
||||
formState={formState}
|
||||
label={t('app.admin.store.product_stock_modal.event_type')} />
|
||||
<FabButton type='submit'>{t('app.admin.store.product_stock_modal.update_stock')} </FabButton>
|
||||
</form>
|
||||
);
|
||||
};
|
@ -84,7 +84,7 @@ export const StoreProductItem: React.FC<StoreProductItemProps> = ({ product, car
|
||||
<span>/ {t('app.public.store_product_item.unit')}</span>
|
||||
</div>
|
||||
}
|
||||
<div className="stock">
|
||||
<div className="stock-label">
|
||||
{productStockStatus(product)}
|
||||
</div>
|
||||
{product.stock.external > 0 &&
|
||||
|
@ -160,7 +160,7 @@ export const StoreProduct: React.FC<StoreProductProps> = ({ productSlug, onError
|
||||
</div>
|
||||
|
||||
<aside>
|
||||
<div className="stock">
|
||||
<div className="stock-label">
|
||||
{productStockStatus(product)}
|
||||
</div>
|
||||
<div className='price'>
|
||||
|
@ -95,6 +95,8 @@
|
||||
@import "modules/store/orders";
|
||||
@import "modules/store/product-categories";
|
||||
@import "modules/store/product-form";
|
||||
@import "modules/store/product-stock-form";
|
||||
@import "modules/store/product-stock-modal";
|
||||
@import "modules/store/products-grid";
|
||||
@import "modules/store/products-list";
|
||||
@import "modules/store/products";
|
||||
|
@ -68,6 +68,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
.stock-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@include text-sm;
|
||||
color: var(--status-color);
|
||||
&::before {
|
||||
content: "";
|
||||
margin-right: 0.8rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
background-color: var(--status-color);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
// Custom scrollbar
|
||||
.u-scrollbar {
|
||||
&::-webkit-scrollbar-track
|
||||
|
@ -52,9 +52,12 @@
|
||||
}
|
||||
|
||||
.header-switch {
|
||||
@include grid-col(10);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 3.2rem;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
label { flex: 0 1 fit-content; }
|
||||
}
|
||||
.price-data-content {
|
||||
display: grid;
|
||||
|
@ -0,0 +1,74 @@
|
||||
.product-stock-form {
|
||||
h4 span { @include text-sm; }
|
||||
|
||||
.store-list {
|
||||
h4 { margin: 0; }
|
||||
}
|
||||
.store-list-header {
|
||||
& > *:not(:first-child) {
|
||||
&::before {
|
||||
content: "";
|
||||
margin: 0 2rem;
|
||||
width: 1px;
|
||||
height: 2rem;
|
||||
background-color: var(--gray-hard-darkest);
|
||||
}
|
||||
}
|
||||
.sort-events {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.sort-stocks {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.threshold-data-content {
|
||||
margin-top: 1.6rem;
|
||||
padding: 1.6rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
gap: 3.2rem;
|
||||
border: 1px solid var(--gray-soft);
|
||||
border-radius: var(--border-radius);
|
||||
label { flex: 0 1 fit-content; }
|
||||
|
||||
}
|
||||
.stock-label {
|
||||
--status-color: var(--alert-light);
|
||||
}
|
||||
|
||||
.stock-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 4.8rem;
|
||||
justify-items: flex-start;
|
||||
align-items: center;
|
||||
padding: 1.6rem;
|
||||
border: 1px solid var(--gray-soft-dark);
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--gray-soft-lightest);
|
||||
|
||||
& > * { flex: 1 1 45%; }
|
||||
button { flex: 0;}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
@include text-base;
|
||||
}
|
||||
.title {
|
||||
@include text-base(600);
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
.group {
|
||||
span {
|
||||
@include text-xs;
|
||||
color: var(--gray-hard-light);
|
||||
}
|
||||
p { @include text-base(600); }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
.product-stock-modal {
|
||||
.movement {
|
||||
margin-bottom: 3.2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
button {
|
||||
flex: 1;
|
||||
padding: 1.6rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: var(--gray-soft-lightest);
|
||||
border: 1px solid var(--gray-soft-dark);
|
||||
color: var(--gray-soft-darkest);
|
||||
@include text-base;
|
||||
&.is-active {
|
||||
border: 1px solid var(--gray-soft-darkest);
|
||||
background-color: var(--gray-hard-darkest);
|
||||
color: var(--gray-soft-lightest);
|
||||
}
|
||||
}
|
||||
button:first-of-type {
|
||||
border-radius: var(--border-radius) 0 0 var(--border-radius);
|
||||
}
|
||||
button:last-of-type {
|
||||
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -49,21 +49,7 @@
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
.stock {
|
||||
grid-area: stock;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@include text-sm;
|
||||
color: var(--status-color);
|
||||
&::before {
|
||||
content: "";
|
||||
margin-right: 0.8rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
background-color: var(--status-color);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
.stock-label { grid-area: stock; }
|
||||
button {
|
||||
grid-area: btn;
|
||||
align-self: flex-end;
|
||||
|
@ -22,9 +22,13 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
& > *:not(:first-child) {
|
||||
margin-left: 2rem;
|
||||
padding-left: 2rem;
|
||||
border-left: 1px solid var(--gray-hard-darkest);
|
||||
&::before {
|
||||
content: "";
|
||||
margin: 0 2rem;
|
||||
width: 1px;
|
||||
height: 2rem;
|
||||
background-color: var(--gray-hard-darkest);
|
||||
}
|
||||
}
|
||||
|
||||
.sort {
|
||||
|
@ -142,20 +142,6 @@
|
||||
background-color: var(--gray-soft-light);
|
||||
border-radius: var(--border-radius-sm);
|
||||
|
||||
.stock {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@include text-sm;
|
||||
color: var(--status-color);
|
||||
&::before {
|
||||
content: "";
|
||||
margin-right: 0.8rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
background-color: var(--status-color);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
.price {
|
||||
p {
|
||||
margin: 0;
|
||||
|
@ -1991,7 +1991,43 @@ en:
|
||||
save: "Save"
|
||||
product_stock_form:
|
||||
low_stock_threshold: "Define a low stock threshold"
|
||||
toggle_stock_threshold: "Activate stock threshold"
|
||||
stock_threshold_toggle: "Activate stock threshold"
|
||||
stock_threshold_information: "<strong>Information</strong></br>Define a low stock threshold and receive a notification when it's reached.<br>Above the threshold, the product is available in the store. When the threshold is reached, the product quantity is labeled as low."
|
||||
low_stock: "Low stock"
|
||||
threshold_level: "Minimum threshold level"
|
||||
threshold_alert: "Notify me when the threshold is reached"
|
||||
event_type: "Events:"
|
||||
stocks: "Stocks:"
|
||||
internal: "Private stock"
|
||||
external: "Public stock"
|
||||
all: "All types"
|
||||
stock_level: "Stock level"
|
||||
events:
|
||||
inward_stock: "Inward stock"
|
||||
returned: "Returned by client"
|
||||
canceled: "Canceled by client"
|
||||
sold: "Sold"
|
||||
missing: "Missing in stock"
|
||||
damaged: "Damaged product"
|
||||
events_history: "Events history"
|
||||
modal_title: "Manage stock"
|
||||
product_stock_modal:
|
||||
internal: "Private stock"
|
||||
external: "Public stock"
|
||||
new_event: "New stock event"
|
||||
addition: "Addition"
|
||||
withdrawal: "Withdrawal"
|
||||
update_stock: "Update stock"
|
||||
event_type: "Events:"
|
||||
stocks: "Stocks:"
|
||||
quantity: "Quantity"
|
||||
events:
|
||||
inward_stock: "Inward stock"
|
||||
returned: "Returned by client"
|
||||
canceled: "Canceled by client"
|
||||
sold: "Sold"
|
||||
missing: "Missing in stock"
|
||||
damaged: "Damaged product"
|
||||
orders:
|
||||
heading: "Orders"
|
||||
create_order: "Create an order"
|
||||
|
Loading…
x
Reference in New Issue
Block a user