1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-20 14:54:15 +01:00

(feat) prevent show product in store if price not set

This commit is contained in:
Sylvain 2022-09-27 15:58:59 +02:00
parent 82d2f99b77
commit f61e784ace
10 changed files with 46 additions and 11 deletions

View File

@ -1,4 +1,5 @@
import axios, { AxiosInstance } from 'axios';
import ParsingLib from '../../lib/parsing';
type Error = { error: string };
@ -48,7 +49,9 @@ function extractHumanReadableMessage (error: string|Error): string {
// iterate through all the keys to build the message
for (const key in error) {
if (Object.prototype.hasOwnProperty.call(error, key)) {
message += `${key} : `;
if (!ParsingLib.isInteger(key)) {
message += `${key} : `;
}
if (error[key] instanceof Array) {
// standard rails messages are stored as {field: [error1, error2]}
// we rebuild them as "field: error1, error2"

View File

@ -59,7 +59,7 @@ export const FormInput = <TFieldValues extends FieldValues, TInputType>({ id, re
{...register(id as FieldPath<TFieldValues>, {
...rules,
valueAsDate: type === 'date',
setValueAs: v => (v === null && nullable) ? null : (type === 'number' ? parseFloat(v) : v),
setValueAs: v => ([null, ''].includes(v) && nullable) ? null : (type === 'number' ? parseFloat(v) : v),
value: defaultValue as FieldPathValue<TFieldValues, FieldPath<TFieldValues>>,
onChange: (e) => { handleChange(e); }
})}

View File

@ -50,7 +50,7 @@ export const ProductForm: React.FC<ProductFormProps> = ({ product, title, onSucc
const { handleSubmit, register, control, formState, setValue, reset } = useForm<Product>({ defaultValues: { ...product } });
const output = useWatch<Product>({ control });
const [isActivePrice, setIsActivePrice] = useState<boolean>(product.id && _.isFinite(product.amount) && product.amount > 0);
const [isActivePrice, setIsActivePrice] = useState<boolean>(product.id && _.isFinite(product.amount));
const [productCategories, setProductCategories] = useState<selectOption[]>([]);
const [machines, setMachines] = useState<checklistOption[]>([]);
const [stockTab, setStockTab] = useState<boolean>(false);
@ -91,12 +91,23 @@ export const ProductForm: React.FC<ProductFormProps> = ({ product, title, onSucc
setValue('slug', slug);
};
/**
* Callback triggered when the user toggles the visibility of the product in the store.
*/
const handleIsActiveChanged = (value: boolean): void => {
if (value) {
setValue('is_active_price', true);
setIsActivePrice(true);
}
};
/**
* Callback triggered when is active price has changed.
*/
const toggleIsActivePrice = (value: boolean) => {
if (!value) {
setValue('amount', null);
setValue('is_active', false);
}
setIsActivePrice(value);
};
@ -262,6 +273,7 @@ export const ProductForm: React.FC<ProductFormProps> = ({ product, title, onSucc
formState={formState}
label={t('app.admin.store.product_form.is_show_in_store')}
tooltip={t('app.admin.store.product_form.active_price_info')}
onChange={handleIsActiveChanged}
className='span-3' />
</div>
@ -280,10 +292,11 @@ export const ProductForm: React.FC<ProductFormProps> = ({ product, title, onSucc
<FormInput id="amount"
type="number"
register={register}
rules={{ required: true, min: 0 }}
rules={{ required: isActivePrice, min: 0 }}
step={0.01}
formState={formState}
label={t('app.admin.store.product_form.price')} />
label={t('app.admin.store.product_form.price')}
nullable />
<FormInput id="quantity_min"
type="number"
rules={{ required: true }}

View File

@ -14,11 +14,25 @@ export default class ParsingLib {
for (const item of value) {
parsedValue.push(ParsingLib.parse(item));
}
} else if (['true', 'false'].includes(value)) {
} else if (ParsingLib.isBoolean(value)) {
parsedValue = (value === 'true');
} else if (parseInt(value, 10).toString() === value) {
} else if (ParsingLib.isInteger(value)) {
parsedValue = parseInt(value, 10);
}
return parsedValue;
};
/**
* Check if the provided string represents an integer
*/
static isInteger = (value: string): boolean => {
return (parseInt(value, 10).toString() === value);
};
/**
* Check if the provided string represents a boolean value
*/
static isBoolean = (value: string): boolean => {
return ['true', 'false'].includes(value);
};
}

View File

@ -95,6 +95,7 @@ export interface Product {
description?: string,
is_active: boolean,
product_category_id?: number,
is_active_price?: boolean,
amount?: number,
quantity_min?: number,
stock: Stock,

View File

@ -23,6 +23,7 @@ class Product < ApplicationRecord
validates :name, :slug, presence: true
validates :slug, uniqueness: true
validates :amount, numericality: { greater_than_or_equal_to: 0, allow_nil: true }
validates :amount, exclusion: { in: [nil], message: I18n.t('.errors.messages.undefined_in_store') }, if: -> { is_active }
scope :active, -> { where(is_active: true) }

View File

@ -30,8 +30,6 @@ class ProductService
if amount.present?
v = amount.to_f
return nil if v.zero?
return v * 100
end
nil

View File

@ -40,7 +40,7 @@ class ProductImageUploader < CarrierWave::Uploader::Base
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
def extension_whitelist
%w[jpg jpeg gif png]
%w[jpg jpeg gif png webp]
end
def content_type_whitelist

View File

@ -10,6 +10,10 @@ en:
week:
one: 'one week'
other: '%{count} weeks'
activerecord:
attributes:
product:
amount: "The price"
errors:
#CarrierWave
messages:
@ -38,6 +42,7 @@ en:
invalid_duration: "The allowed duration must be between 1 day and 1 year. Your period is %{DAYS} days long."
must_be_in_the_past: "The period must be strictly prior to today's date."
registration_disabled: "Registration is disabled"
undefined_in_store: "must be defined to make the product available in the store"
apipie:
api_documentation: "API Documentation"
code: "HTTP code"

View File

@ -212,4 +212,4 @@ en:
default: "%a, %d %b %Y %H:%M:%S %z"
long: "%B %d, %Y %H:%M"
short: "%d %b %H:%M"
pm: pm
pm: pm