Merge pull request #466 from ZcashFoundation/proposal-milestone-date-validation
Proposal milestone date validation
This commit is contained in:
commit
be98eec24c
|
@ -309,17 +309,6 @@ class Proposal(db.Model):
|
||||||
|
|
||||||
payout_total += p
|
payout_total += p
|
||||||
|
|
||||||
try:
|
|
||||||
present = datetime.datetime.today().replace(day=1)
|
|
||||||
if present > milestone.date_estimated:
|
|
||||||
raise ValidationException("Milestone date estimate must be in the future ")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
current_app.logger.warn(
|
|
||||||
f"Unexpected validation error - client prohibits {e}"
|
|
||||||
)
|
|
||||||
raise ValidationException("Date estimate is not a valid datetime")
|
|
||||||
|
|
||||||
if payout_total != 100.0:
|
if payout_total != 100.0:
|
||||||
raise ValidationException("Payout percentages of milestones must add up to exactly 100%")
|
raise ValidationException("Payout percentages of milestones must add up to exactly 100%")
|
||||||
|
|
||||||
|
@ -358,6 +347,14 @@ class Proposal(db.Model):
|
||||||
# Then run through regular validation
|
# Then run through regular validation
|
||||||
Proposal.simple_validate(vars(self))
|
Proposal.simple_validate(vars(self))
|
||||||
|
|
||||||
|
# only do this when user submits for approval, there is a chance the dates will
|
||||||
|
# be passed by the time admin approval / user publishing occurs
|
||||||
|
def validate_milestone_dates(self):
|
||||||
|
present = datetime.datetime.today().replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
for milestone in self.milestones:
|
||||||
|
if present > milestone.date_estimated:
|
||||||
|
raise ValidationException("Milestone date estimate must be in the future ")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create(**kwargs):
|
def create(**kwargs):
|
||||||
Proposal.simple_validate(kwargs)
|
Proposal.simple_validate(kwargs)
|
||||||
|
@ -475,6 +472,7 @@ class Proposal(db.Model):
|
||||||
# state: status (DRAFT || REJECTED) -> (PENDING || STAKING)
|
# state: status (DRAFT || REJECTED) -> (PENDING || STAKING)
|
||||||
def submit_for_approval(self):
|
def submit_for_approval(self):
|
||||||
self.validate_publishable()
|
self.validate_publishable()
|
||||||
|
self.validate_milestone_dates()
|
||||||
allowed_statuses = [ProposalStatus.DRAFT, ProposalStatus.REJECTED]
|
allowed_statuses = [ProposalStatus.DRAFT, ProposalStatus.REJECTED]
|
||||||
# specific validation
|
# specific validation
|
||||||
if self.status not in allowed_statuses:
|
if self.status not in allowed_statuses:
|
||||||
|
|
|
@ -56,6 +56,7 @@ typings/
|
||||||
|
|
||||||
# dotenv environment variables file
|
# dotenv environment variables file
|
||||||
.env
|
.env
|
||||||
|
.env.testnet
|
||||||
|
|
||||||
# next.js build output
|
# next.js build output
|
||||||
.next
|
.next
|
||||||
|
|
|
@ -161,7 +161,9 @@ const MilestoneFields = ({
|
||||||
}
|
}
|
||||||
format="MMMM YYYY"
|
format="MMMM YYYY"
|
||||||
allowClear={false}
|
allowClear={false}
|
||||||
onChange={time => onChange(index, { ...milestone, dateEstimated: time.unix() })}
|
onChange={time =>
|
||||||
|
onChange(index, { ...milestone, dateEstimated: time.startOf('month').unix() })
|
||||||
|
}
|
||||||
disabled={milestone.immediatePayout}
|
disabled={milestone.immediatePayout}
|
||||||
disabledDate={current => {
|
disabledDate={current => {
|
||||||
if (!previousMilestoneDateEstimate) {
|
if (!previousMilestoneDateEstimate) {
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
import { ProposalDraft, STATUS, MILESTONE_STAGE, PROPOSAL_ARBITER_STATUS } from 'types';
|
import {
|
||||||
|
ProposalDraft,
|
||||||
|
STATUS,
|
||||||
|
MILESTONE_STAGE,
|
||||||
|
PROPOSAL_ARBITER_STATUS,
|
||||||
|
CreateMilestone,
|
||||||
|
} from 'types';
|
||||||
|
import moment from 'moment';
|
||||||
import { User } from 'types';
|
import { User } from 'types';
|
||||||
import {
|
import {
|
||||||
getAmountError,
|
getAmountError,
|
||||||
|
@ -127,23 +134,9 @@ export function getCreateErrors(
|
||||||
// Milestones
|
// Milestones
|
||||||
if (milestones) {
|
if (milestones) {
|
||||||
let cumulativeMilestonePct = 0;
|
let cumulativeMilestonePct = 0;
|
||||||
|
let lastMsEst: CreateMilestone['dateEstimated'] = 0;
|
||||||
const milestoneErrors = milestones.map((ms, idx) => {
|
const milestoneErrors = milestones.map((ms, idx) => {
|
||||||
if (!ms.title) {
|
// check payout first so we collect the cumulativePayout even if other fields are invalid
|
||||||
return 'Title is required';
|
|
||||||
} else if (ms.title.length > 40) {
|
|
||||||
return 'Title length can only be 40 characters maximum';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ms.content) {
|
|
||||||
return 'Description is required';
|
|
||||||
} else if (ms.content.length > 200) {
|
|
||||||
return 'Description can only be 200 characters maximum';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ms.dateEstimated) {
|
|
||||||
return 'Estimate date is required';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ms.payoutPercent) {
|
if (!ms.payoutPercent) {
|
||||||
return 'Payout percent is required';
|
return 'Payout percent is required';
|
||||||
} else if (Number.isNaN(parseInt(ms.payoutPercent, 10))) {
|
} else if (Number.isNaN(parseInt(ms.payoutPercent, 10))) {
|
||||||
|
@ -158,6 +151,37 @@ export function getCreateErrors(
|
||||||
|
|
||||||
// Last one shows percentage errors
|
// Last one shows percentage errors
|
||||||
cumulativeMilestonePct += parseInt(ms.payoutPercent, 10);
|
cumulativeMilestonePct += parseInt(ms.payoutPercent, 10);
|
||||||
|
|
||||||
|
if (!ms.title) {
|
||||||
|
return 'Title is required';
|
||||||
|
} else if (ms.title.length > 40) {
|
||||||
|
return 'Title length can only be 40 characters maximum';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ms.content) {
|
||||||
|
return 'Description is required';
|
||||||
|
} else if (ms.content.length > 200) {
|
||||||
|
return 'Description can only be 200 characters maximum';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ms.dateEstimated) {
|
||||||
|
return 'Estimate date is required';
|
||||||
|
} else {
|
||||||
|
// FE validation on milestone estimation
|
||||||
|
if (
|
||||||
|
ms.dateEstimated <
|
||||||
|
moment(Date.now())
|
||||||
|
.startOf('month')
|
||||||
|
.unix()
|
||||||
|
) {
|
||||||
|
return 'Estimate date should be in the future';
|
||||||
|
}
|
||||||
|
if (ms.dateEstimated <= lastMsEst) {
|
||||||
|
return 'Estimate date should be later than previous estimate date';
|
||||||
|
}
|
||||||
|
lastMsEst = ms.dateEstimated;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
idx === milestones.length - 1 &&
|
idx === milestones.length - 1 &&
|
||||||
cumulativeMilestonePct !== 100 &&
|
cumulativeMilestonePct !== 100 &&
|
||||||
|
|
Loading…
Reference in New Issue