from wtforms.fields import ( BooleanField, DecimalField, FieldList, FormField, StringField, HiddenField, ) from wtforms.fields.html5 import DateField from wtforms.validators import ( Required, Length, Optional, NumberRange, ValidationError, ) from flask_wtf import FlaskForm import numbers from atat.forms.validators import Number, AlphaNumeric from .data import JEDI_CLIN_TYPES from .fields import SelectField from .forms import BaseForm, remove_empty_string from atat.utils.localization import translate from flask import current_app as app MAX_CLIN_AMOUNT = 1_000_000_000 def coerce_enum(enum_inst): if getattr(enum_inst, "value", None): return enum_inst.value else: return enum_inst def validate_funding(form, field): if ( isinstance(form.total_amount.data, numbers.Number) and isinstance(field.data, numbers.Number) and form.total_amount.data < field.data ): raise ValidationError( translate("forms.task_order.clin_funding_errors.obligated_amount_error") ) def validate_date_in_range(form, field): contract_start = app.config.get("CONTRACT_START_DATE") contract_end = app.config.get("CONTRACT_END_DATE") if field.data and (field.data < contract_start or field.data > contract_end): raise ValidationError( translate( "forms.task_order.pop_errors.range", { "start": contract_start.strftime("%b %d, %Y"), "end": contract_end.strftime("%b %d, %Y"), }, ) ) def remove_dashes(value): return value.replace("-", "") if value else None def coerce_upper(value): return value.upper() if value else None class CLINForm(FlaskForm): jedi_clin_type = SelectField( translate("task_orders.form.clin_type_label"), choices=JEDI_CLIN_TYPES, coerce=coerce_enum, ) number = StringField( label=translate("task_orders.form.clin_number_label"), validators=[Number(), Length(max=4)], ) start_date = DateField( translate("task_orders.form.pop_start"), description=translate("task_orders.form.pop_example"), format="%m/%d/%Y", validators=[validate_date_in_range], ) end_date = DateField( translate("task_orders.form.pop_end"), description=translate("task_orders.form.pop_example"), format="%m/%d/%Y", validators=[validate_date_in_range], ) total_amount = DecimalField( label=translate("task_orders.form.total_funds_label"), validators=[ NumberRange( 0, MAX_CLIN_AMOUNT, translate("forms.task_order.clin_funding_errors.funding_range_error"), ) ], ) obligated_amount = DecimalField( label=translate("task_orders.form.obligated_funds_label"), validators=[ validate_funding, NumberRange( 0, MAX_CLIN_AMOUNT, translate("forms.task_order.clin_funding_errors.funding_range_error"), ), ], ) def validate(self, *args, **kwargs): valid = super().validate(*args, **kwargs) if ( self.start_date.data and self.end_date.data and self.start_date.data > self.end_date.data ): self.start_date.errors.append( translate("forms.task_order.pop_errors.date_order") ) valid = False return valid class AttachmentForm(BaseForm): filename = HiddenField( id="attachment_filename", validators=[ Length( max=100, message=translate("forms.attachment.filename.length_error") ), AlphaNumeric(), ], ) object_name = HiddenField( id="attachment_object_name", validators=[ Length( max=40, message=translate("forms.attachment.object_name.length_error") ), AlphaNumeric(), ], ) accept = ".pdf,application/pdf" def validate(self, *args, **kwargs): return super().validate(*args, **{**kwargs, "flash_invalid": False}) class TaskOrderForm(BaseForm): number = StringField( label=translate("forms.task_order.number_description"), filters=[remove_empty_string, remove_dashes, coerce_upper], validators=[AlphaNumeric(), Length(min=13, max=17), Optional()], ) pdf = FormField(AttachmentForm) clins = FieldList(FormField(CLINForm)) class SignatureForm(BaseForm): signature = BooleanField( translate("task_orders.sign.digital_signature_description"), validators=[Required()], ) confirm = BooleanField( translate("task_orders.sign.confirmation_description"), validators=[Required()], )