Initial commit: Clean Mold Cost Calculator application
- Flask-based mold cost calculation application - PostgreSQL database with SQLAlchemy ORM - Docker/Podman containerization - Comprehensive documentation in docs/ - Clean, organized codebase without obsolete files - Production-ready deployment configuration - Enhanced pytest configuration with coverage reporting - Master/Development branch structure
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
from .auth import LoginForm, RegistrationForm
|
||||
from .quotation import QuotationForm
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, PasswordField, SubmitField
|
||||
from wtforms.validators import DataRequired, Email, EqualTo
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
__all__ = ['LoginForm', 'RegistrationForm', 'QuotationForm']
|
||||
|
||||
class RequestPasswordResetForm(FlaskForm):
|
||||
email = StringField('Email', validators=[DataRequired(), Email()])
|
||||
submit = SubmitField('Request Password Reset')
|
||||
|
||||
class ResetPasswordForm(FlaskForm):
|
||||
password = PasswordField('Password', validators=[DataRequired()])
|
||||
password2 = PasswordField('Repeat Password', validators=[DataRequired(), EqualTo('password')])
|
||||
submit = SubmitField('Reset Password')
|
||||
@@ -0,0 +1,108 @@
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import (
|
||||
StringField,
|
||||
PasswordField,
|
||||
BooleanField,
|
||||
SubmitField
|
||||
)
|
||||
from wtforms.validators import (
|
||||
DataRequired,
|
||||
Email,
|
||||
Length,
|
||||
ValidationError,
|
||||
EqualTo
|
||||
)
|
||||
from sqlalchemy import func
|
||||
from app.models import User
|
||||
import re
|
||||
|
||||
class LoginForm(FlaskForm):
|
||||
email = StringField('Email', validators=[
|
||||
DataRequired(),
|
||||
Email(),
|
||||
Length(max=120)
|
||||
])
|
||||
password = PasswordField('Password', validators=[
|
||||
DataRequired(),
|
||||
Length(min=8)
|
||||
])
|
||||
remember_me = BooleanField('Remember Me')
|
||||
submit = SubmitField('Log In')
|
||||
|
||||
class RegistrationForm(FlaskForm):
|
||||
username = StringField('Username', validators=[
|
||||
DataRequired(),
|
||||
Length(min=3, max=64)
|
||||
])
|
||||
email = StringField('Email', validators=[
|
||||
DataRequired(),
|
||||
Email(),
|
||||
Length(max=120)
|
||||
])
|
||||
company = StringField('Company', validators=[
|
||||
DataRequired(),
|
||||
Length(max=100)
|
||||
])
|
||||
password = PasswordField('Password', validators=[
|
||||
DataRequired(),
|
||||
Length(min=8)
|
||||
])
|
||||
password_confirm = PasswordField('Confirm Password', validators=[
|
||||
DataRequired(),
|
||||
EqualTo('password', message='Passwords must match')
|
||||
])
|
||||
terms = BooleanField('I agree to the terms and conditions', validators=[
|
||||
DataRequired(message='You must agree to the terms and conditions')
|
||||
])
|
||||
submit = SubmitField('Register')
|
||||
|
||||
def validate_username(self, username):
|
||||
if not re.match(r'^[a-zA-Z0-9_-]+$', username.data):
|
||||
raise ValidationError('Username can only contain letters, numbers, underscores and hyphens')
|
||||
user = User.query.filter_by(username=username.data).first()
|
||||
if user:
|
||||
raise ValidationError('Username already taken')
|
||||
|
||||
def validate_email(self, email):
|
||||
user = User.query.filter_by(email=email.data).first()
|
||||
if user:
|
||||
raise ValidationError('Email already registered')
|
||||
|
||||
def validate_password(self, password):
|
||||
if not re.search(r'[A-Z]', password.data):
|
||||
raise ValidationError('Password must contain at least one uppercase letter')
|
||||
if not re.search(r'[a-z]', password.data):
|
||||
raise ValidationError('Password must contain at least one lowercase letter')
|
||||
if not re.search(r'[0-9]', password.data):
|
||||
raise ValidationError('Password must contain at least one number')
|
||||
if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password.data):
|
||||
raise ValidationError('Password must contain at least one special character')
|
||||
|
||||
class RequestPasswordResetForm(FlaskForm):
|
||||
email = StringField('Email', validators=[
|
||||
DataRequired(),
|
||||
Email(),
|
||||
Length(max=120)
|
||||
])
|
||||
submit = SubmitField('Request Password Reset')
|
||||
|
||||
class ResetPasswordForm(FlaskForm):
|
||||
password = PasswordField('New Password', validators=[
|
||||
DataRequired(),
|
||||
Length(min=8)
|
||||
])
|
||||
password_confirm = PasswordField('Confirm New Password', validators=[
|
||||
DataRequired(),
|
||||
EqualTo('password', message='Passwords must match')
|
||||
])
|
||||
submit = SubmitField('Reset Password')
|
||||
|
||||
def validate_password(self, password):
|
||||
if not re.search(r'[A-Z]', password.data):
|
||||
raise ValidationError('Password must contain at least one uppercase letter')
|
||||
if not re.search(r'[a-z]', password.data):
|
||||
raise ValidationError('Password must contain at least one lowercase letter')
|
||||
if not re.search(r'[0-9]', password.data):
|
||||
raise ValidationError('Password must contain at least one number')
|
||||
if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password.data):
|
||||
raise ValidationError('Password must contain at least one special character')
|
||||
@@ -0,0 +1,193 @@
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import (
|
||||
StringField,
|
||||
SelectField,
|
||||
DateField,
|
||||
SubmitField
|
||||
)
|
||||
from wtforms.validators import (
|
||||
DataRequired,
|
||||
Length,
|
||||
ValidationError
|
||||
)
|
||||
|
||||
def validate_tooling_id_format(form, field):
|
||||
"""Custom validator to ensure tooling_id is exactly 5 digits"""
|
||||
if not field.data.isdigit() or len(field.data) != 5:
|
||||
raise ValidationError('Tooling ID must be exactly 5 digits')
|
||||
|
||||
class QuotationForm(FlaskForm):
|
||||
# Factory Information
|
||||
mold_shop_name = StringField('Mold Shop Name', validators=[DataRequired()])
|
||||
mold_shop_country = SelectField('Mold Shop Country', validators=[DataRequired()], choices=[
|
||||
('', 'Select Country'),
|
||||
('China', 'China'),
|
||||
('Vietnam', 'Vietnam'),
|
||||
('Indonesia', 'Indonesia')
|
||||
])
|
||||
t1_shoe_factory_name = StringField('T1 Shoe Factory Name', validators=[DataRequired()])
|
||||
t1_shoe_factory_country = SelectField('T1 Shoe Factory Country', validators=[DataRequired()], choices=[
|
||||
('', 'Select Country'),
|
||||
('China', 'China'),
|
||||
('Vietnam', 'Vietnam'),
|
||||
('Indonesia', 'Indonesia')
|
||||
])
|
||||
t2_component_factory_name = StringField('T2 Component Factory Name', validators=[DataRequired()])
|
||||
t2_component_factory_country = SelectField('T2 Component Factory Country', validators=[DataRequired()], choices=[
|
||||
('', 'Select Country'),
|
||||
('China', 'China'),
|
||||
('Vietnam', 'Vietnam'),
|
||||
('Indonesia', 'Indonesia')
|
||||
])
|
||||
|
||||
# Mold Information
|
||||
model_name = StringField('Model Name', validators=[DataRequired()])
|
||||
tooling_id = StringField('Tooling ID', validators=[DataRequired(), Length(min=5, max=5), validate_tooling_id_format], id='tooling_id')
|
||||
season = StringField('Season', validators=[DataRequired()])
|
||||
|
||||
# Define stage-type mapping
|
||||
STAGE_TYPE_MAPPING = {
|
||||
'CR0': ['Sample'],
|
||||
'CR1': ['Sample'],
|
||||
'CR2': ['Sample'],
|
||||
'SMS': ['A Set'],
|
||||
'commercialization': ['A Set'],
|
||||
'production': ['Additional']
|
||||
}
|
||||
|
||||
# Define the mapping of main types to sub-types
|
||||
MOLD_TYPE_MAPPING = {
|
||||
'Rubber': ['Flat Outsole', 'Cupsole', 'Rubber Sole'],
|
||||
'CMEVA': ['2 Plates Mold', '3 Plates Mold', '2 Plates with in-mold channel', 'Breathable + in-mold channel'],
|
||||
'IMEVA': ['2 Plates Mold', '3 Plates Mold'],
|
||||
'Sandal': ['Sandal with Upper Bandage', '2 Plate without Upper Bandage'],
|
||||
'Co-shot mold': ['Co-shot mold', 'Foaming mold'],
|
||||
'PU': ['2 Plates Mold', '3 Plates Mold']
|
||||
}
|
||||
|
||||
# Define the mapping of sub-types to their specific options
|
||||
MOLD_SPECIFIC_TYPE_OPTIONS = {
|
||||
'Flat Outsole': ['Flat', 'Flat with Top/Heel Tip'],
|
||||
'Cupsole': ['Top PL', 'Visible PL'],
|
||||
'Rubber Sole': ['Top PL', 'Short PL (Heel/Forefoot)', 'Visible PL', 'Wave PL']
|
||||
}
|
||||
|
||||
mold_main_type = SelectField('Mold Main Type', validators=[DataRequired()], choices=[
|
||||
('', 'Select Main Type'),
|
||||
('Rubber', 'Rubber'),
|
||||
('CMEVA', 'CMEVA'),
|
||||
('IMEVA', 'IMEVA'),
|
||||
('Sandal', 'Sandal'),
|
||||
('Co-shot mold', 'Co-shot mold'),
|
||||
('PU', 'PU')
|
||||
], id='mold_main_type')
|
||||
|
||||
mold_sub_type = SelectField('Mold Sub Type', validators=[DataRequired()], choices=[
|
||||
('', 'Select Sub Type')
|
||||
], id='mold_sub_type')
|
||||
|
||||
mold_specific_type = SelectField('Mold Specific Type', validators=[], choices=[
|
||||
('', 'Select Specific Type')
|
||||
], id='mold_specific_type', coerce=str, default='')
|
||||
|
||||
stage = SelectField('Stage', validators=[DataRequired()], choices=[
|
||||
('', 'Select Stage'),
|
||||
('CR0', 'CR0'),
|
||||
('CR1', 'CR1'),
|
||||
('CR2', 'CR2'),
|
||||
('SMS', 'SMS'),
|
||||
('commercialization', 'Commercialization'),
|
||||
('production', 'Production')
|
||||
], id='stage')
|
||||
|
||||
type = SelectField('Type', validators=[DataRequired()], choices=[
|
||||
('', 'Select Type'),
|
||||
('Sample', 'Sample'),
|
||||
('A Set', 'A Set'),
|
||||
('Additional', 'Additional')
|
||||
], id='type')
|
||||
|
||||
# Dates and Approvals
|
||||
issue_date = DateField('Issue Date', validators=[DataRequired()])
|
||||
approved_by_mold_shop = StringField('Approved by (Mold Shop Representative)', validators=[DataRequired()])
|
||||
approved_by_t1_tooling = StringField('Approved by (T1 Tooling Representative)', validators=[DataRequired()])
|
||||
approved_by_lo_tooling = StringField('Approved by (LO Tooling)', validators=[DataRequired()])
|
||||
|
||||
# Mold Cost
|
||||
sliders_count = SelectField('Number of Sliders', validators=[DataRequired()], choices=[
|
||||
('', 'Select Number of Sliders'),
|
||||
('0', '0'),
|
||||
('1', '1'),
|
||||
('2', '2'),
|
||||
('3', '3'),
|
||||
('4', '4')
|
||||
])
|
||||
cavity_count = SelectField('Number of Cavities', validators=[DataRequired()], choices=[
|
||||
('', 'Select Number of Pairs'),
|
||||
('0.5', '0.5 pair'),
|
||||
('1', '1 pair'),
|
||||
('1.5', '1.5 pair'),
|
||||
('2', '2 pair'),
|
||||
('2.5', '2.5 pair'),
|
||||
('3', '3 pair')
|
||||
])
|
||||
digital_texture_type = SelectField('Digital Texture', validators=[DataRequired()], choices=[
|
||||
('', 'Select Texture Type'),
|
||||
('adidas_digital', 'adidas digital texture'),
|
||||
('customized texture', 'customized texture'),
|
||||
('laser', 'laser texture')
|
||||
])
|
||||
complexity_high_sidewall = SelectField('High Sidewall >60mm', validators=[DataRequired()], choices=[('no', 'No'), ('yes', 'Yes')])
|
||||
submit = SubmitField('Calculate')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(QuotationForm, self).__init__(*args, **kwargs)
|
||||
# Set initial choices for mold_sub_type based on default mold_main_type
|
||||
if self.mold_main_type.data in self.MOLD_TYPE_MAPPING:
|
||||
self.mold_sub_type.choices = [('', 'Select Sub Type')] + [(x, x) for x in self.MOLD_TYPE_MAPPING[self.mold_main_type.data]]
|
||||
|
||||
def validate_mold_sub_type(self, field):
|
||||
main_type = self.mold_main_type.data
|
||||
print(f"Validating sub_type: {field.data} for main_type: {main_type}")
|
||||
if main_type and main_type in self.MOLD_TYPE_MAPPING:
|
||||
valid_sub_types = self.MOLD_TYPE_MAPPING[main_type]
|
||||
print(f"Valid sub_types for {main_type}: {valid_sub_types}")
|
||||
if field.data not in valid_sub_types and field.data != '':
|
||||
print(f"Invalid sub_type: {field.data}. Valid types: {valid_sub_types}")
|
||||
raise ValidationError('Invalid sub-type for selected main type')
|
||||
|
||||
def validate_mold_specific_type(self, field):
|
||||
"""Validate that the selected specific type is valid for the selected sub type."""
|
||||
sub_type = self.mold_sub_type.data
|
||||
if not sub_type:
|
||||
field.data = ''
|
||||
return
|
||||
|
||||
# Get valid specific types for the selected sub type
|
||||
valid_specific_types = self.MOLD_SPECIFIC_TYPE_OPTIONS.get(sub_type, [])
|
||||
|
||||
# If there are no specific types for this sub-type, the field should be empty
|
||||
if not valid_specific_types:
|
||||
field.data = '' # Always clear the field if it shouldn't have a value
|
||||
return
|
||||
|
||||
# If there are specific types, validate the selection
|
||||
if field.data is None or field.data == '':
|
||||
raise ValidationError(f'Specific type is required for {sub_type}')
|
||||
if field.data not in valid_specific_types:
|
||||
raise ValidationError(f'Invalid specific type for {sub_type}. Valid options are: {", ".join(valid_specific_types)}')
|
||||
|
||||
def validate_type(self, field):
|
||||
stage = self.stage.data
|
||||
print(f"Validating type: {field.data} for stage: {stage}")
|
||||
if stage and stage in self.STAGE_TYPE_MAPPING:
|
||||
valid_types = self.STAGE_TYPE_MAPPING[stage]
|
||||
print(f"Valid types for {stage}: {valid_types}")
|
||||
if field.data not in valid_types and field.data != '':
|
||||
print(f"Invalid type: {field.data}")
|
||||
raise ValidationError(f'Invalid type for {stage} stage. Allowed types: {", ".join(valid_types)}')
|
||||
elif field.data != '':
|
||||
raise ValidationError('Please select a stage first')
|
||||
|
||||
def validate_tooling_id(self, field):
|
||||
validate_tooling_id_format(self, field)
|
||||
Reference in New Issue
Block a user