diff --git a/atst/routes/task_orders/new.py b/atst/routes/task_orders/new.py index b8284a35..ce699603 100644 --- a/atst/routes/task_orders/new.py +++ b/atst/routes/task_orders/new.py @@ -16,6 +16,7 @@ def render_task_orders_edit(portfolio_id=None, task_order_id=None, form=None): portfolio_id = task_order.portfolio_id render_args["form"] = form or TaskOrderForm(**task_order.to_dictionary()) render_args["task_order_id"] = task_order_id + render_args["task_order"] = task_order else: render_args["form"] = form or TaskOrderForm() diff --git a/js/components/clin_fields.js b/js/components/clin_fields.js index d96ac828..b0805229 100644 --- a/js/components/clin_fields.js +++ b/js/components/clin_fields.js @@ -1,7 +1,11 @@ import DateSelector from './date_selector' +import { emitEvent } from '../lib/emitters' import optionsinput from './options_input' import textinput from './text_input' +const JEDI_CLIN_TYPE = 'jedi_clin_type' +const OBLIGATED_AMOUNT = 'obligated_amount' + export default { name: 'clin-fields', @@ -17,6 +21,11 @@ export default { type: Number, default: 0, }, + initialClinType: String, + initialAmount: { + type: Number, + default: 0, + }, }, data: function() { @@ -27,9 +36,23 @@ export default { clinIndex: this.initialClinIndex, indexOffset: this.initialLoaCount, loas: loas, + clinType: this.initialClinType, + amount: this.initialAmount || 0, } }, + mounted: function() { + this.$root.$on('field-change', this.handleFieldChange) + }, + + created: function() { + emitEvent('clin-change', this, { + id: this._uid, + clinType: this.clinType, + amount: this.initialAmount, + }) + }, + methods: { addLoa: function(event) { ++this.loas @@ -38,5 +61,25 @@ export default { loaIndex: function(index) { return index + this.indexOffset - 1 }, + + clinChangeEvent: function() { + emitEvent('clin-change', this, { + id: this._uid, + clinType: this.clinType, + amount: this.amount, + }) + }, + + handleFieldChange: function(event) { + if (this._uid === event.parent_uid) { + if (event.name.includes(JEDI_CLIN_TYPE)) { + this.clinType = event.value + this.clinChangeEvent() + } else if (event.name.includes(OBLIGATED_AMOUNT)) { + this.amount = parseFloat(event.value) + this.clinChangeEvent() + } + } + }, }, } diff --git a/js/components/forms/to_form.js b/js/components/forms/to_form.js index 6903ebe4..e6429226 100644 --- a/js/components/forms/to_form.js +++ b/js/components/forms/to_form.js @@ -6,6 +6,7 @@ import FormMixin from '../../mixins/form' import optionsinput from '../options_input' import SemiCollapsibleText from '../semi_collapsible_text' import textinput from '../text_input' +import TotalsBox from '../totals_box' import uploadinput from '../upload_input' export default { @@ -19,11 +20,14 @@ export default { optionsinput, SemiCollapsibleText, textinput, + TotalsBox, uploadinput, }, props: { initialClinCount: Number, + initialObligated: Number, + initialTotal: Number, }, data: function() { @@ -33,14 +37,39 @@ export default { return { clins, clinIndex, + obligated: this.initialObligated || 0, + total: this.initialTotal || 0, + clinChildren: {}, } }, + mounted: function() { + this.$root.$on('clin-change', this.calculateClinAmounts) + }, + methods: { addClin: function(event) { ++this.clins ++this.clinIndex }, + + calculateClinAmounts: function(event) { + this.clinChildren[event.id] = { + amount: event.amount, + type: event.clinType, + } + + let newTotal = 0 + let newObligated = 0 + Object.values(this.clinChildren).forEach(function(clin) { + newTotal += clin.amount + if (clin.type.includes('1', '3')) { + newObligated += clin.amount + } + }) + this.total = newTotal + this.obligated = newObligated + }, }, directives: { diff --git a/js/components/totals_box.js b/js/components/totals_box.js new file mode 100644 index 00000000..6bc64c40 --- /dev/null +++ b/js/components/totals_box.js @@ -0,0 +1,20 @@ +import { formatDollars } from '../lib/dollars' + +export default { + name: 'totalsbox', + + props: { + name: String, + obligated: Number, + contractAmount: Number, + }, + + computed: { + formattedObligated: function() { + return formatDollars(this.obligated) + }, + formattedContractAmount: function() { + return formatDollars(this.contractAmount) + }, + }, +} diff --git a/js/index.js b/js/index.js index 42a7b7c5..91706a2f 100644 --- a/js/index.js +++ b/js/index.js @@ -41,6 +41,7 @@ import DeleteConfirmation from './components/delete_confirmation' import NewEnvironment from './components/forms/new_environment' import EnvironmentRole from './components/environment_role' import SemiCollapsibleText from './components/semi_collapsible_text' +import TotalsBox from './components/totals_box' import ToForm from './components/forms/to_form' import ClinFields from './components/clin_fields' @@ -85,6 +86,7 @@ const app = new Vue({ NewEnvironment, EnvironmentRole, SemiCollapsibleText, + TotalsBox, ToForm, ClinFields, }, diff --git a/templates/task_orders/edit.html b/templates/task_orders/edit.html index 5ee999b1..fb133a4e 100644 --- a/templates/task_orders/edit.html +++ b/templates/task_orders/edit.html @@ -59,6 +59,8 @@
@@ -102,7 +104,15 @@ {% endif %}
{{ form.csrf_token }} - + + {% set obligated = task_order.total_obligated_funds if task_order else 0 %} + {% set total = task_order.total_contract_amount if task_order else 0 %} + +
{% call StickyCTA(text="Add Funding") %} @@ -150,7 +160,10 @@

- +
@@ -205,6 +218,7 @@
+
@@ -382,8 +396,27 @@
{{ UploadInput(form.pdf, watch=True) }}
- {{ TotalsBox(task_order=task_order) }} + + +
+
Total obligated funds
+
+
This is the funding allocated to cloud services. It may be 100% or a portion of the total task order budget.
+ +
+ +
Total contract amount
+
+
This is the value of all funds obligated for this contract, including -- but not limited to -- funds obligated for the cloud.
+
+
+
+