def start_work(contract_id=-1): contract = ContractBase.query.get(contract_id) contract = contract if contract else ContractBase() form = NewContractForm(obj=contract) if form.validate_on_submit(): if contract_id == -1: contract, _ = get_or_create( db.session, ContractBase, description=form.data.get('description'), department=form.data.get('department'), is_visible=False ) else: contract = ContractBase.clone(contract) contract.description = form.data.get('description') contract.department = form.data.get('department') db.session.add(contract) db.session.commit() assigned = assign_a_contract(contract, form.data.get('flow'), form.data.get('assigned').id, clone=False) db.session.commit() if assigned: flash('Successfully assigned {} to {}!'.format(assigned.description, assigned.assigned.email), 'alert-success') return redirect(url_for('conductor.detail', contract_id=assigned.id)) else: flash("That flow doesn't exist!", 'alert-danger') return render_template('conductor/new.html', form=form, contract_id=contract_id)
def search_feedback(search_for): '''Provide feedback about an empty search This page is only viewable in the event of a search that returns 0 results. :param search_for: Search term. .. seealso :: :py:func:`~purchasing.scout.util.feedback_handler` for information on how the feedback is processed and handled ''' contract = ContractBase(description='Search term: ' + search_for) return feedback_handler(contract, search_for=search_for)
def assign_a_contract(contract, flow, user, clone=True): # if we don't have a flow, stop and throw an error if not flow: return False # if the contract is already assigned, # resassign it and continue on if contract.assigned_to and not contract.completed_last_stage(): contract.assigned_to = user.id db.session.commit() current_app.logger.info('CONDUCTOR ASSIGN - old contract "{}" assigned to {} with flow {}'.format( contract.description, contract.assigned.email, contract.flow.flow_name )) return contract # otherwise, it's new work. perform the following: # 1. create a cloned version of the contract # 2. create the relevant contract stages # 3. transition into the first stage # 4. assign the contract to the user else: if clone: contract = ContractBase.clone(contract) db.session.add(contract) db.session.commit() try: stages, _, _ = create_contract_stages(flow.id, contract.id, contract=contract) actions = contract.transition(user) for i in actions: db.session.add(i) db.session.flush() except IntegrityError: # we already have the sequence for this, so just # rollback and pass db.session.rollback() pass contract.assigned_to = user.id db.session.commit() current_app.logger.info('CONDUCTOR ASSIGN - new contract "{}" assigned to {} with flow {}'.format( contract.description, contract.assigned.email, contract.flow.flow_name )) return contract
def test_import_line_items(self): ''' Test that award information is scraped properly. ''' muni = Company.create(**dict(company_name='U.S. Municipal Supply, Inc.')) chemung = Company.create(**dict(company_name='Chemung Supply Corporation')) pathmaster = Company.create(**dict(company_name='Path Master, Inc., Co.')) new_contract = ContractBase.create( **dict(properties=[ContractProperty(key='foo', value='6965')], description='foo') ) with open(current_app.config.get('PROJECT_ROOT') + '/purchasing_test/mock/award.html', 'r') as f: line_item_page = BeautifulSoup( f.read(), from_encoding='windows-1252' ) _line_items = grab_line_items(line_item_page) self.assertEquals(len(_line_items), 14) contract = get_contract( 'Sign Post, Square Tubes, Brackets, Etc.', 'IFB-6965' ) self.assertTrue(contract is not None) self.assertEquals(contract.id, new_contract.id) save_line_item(_line_items, contract) self.assertEquals(LineItem.query.count(), len(_line_items)) # assert that our ids made it in property for item in LineItem.query.all(): self.assertTrue(item.manufacturer is not None) self.assertTrue(item.model_number is not None) self.assertEquals(item.contract_id, contract.id) if 'muni' in item.company_name.lower(): self.assertEquals(item.company_id, muni.id) elif 'chem' in item.company_name.lower(): self.assertEquals(item.company_id, chemung.id) else: self.assertEquals(item.company_id, pathmaster.id)
def test_import_line_items(self): muni = Company.create(**dict(company_name='U.S. Municipal Supply, Inc.')) chemung = Company.create(**dict(company_name='Chemung Supply Corporation')) pathmaster = Company.create(**dict(company_name='Path Master, Inc., Co.')) new_contract = ContractBase.create( **dict(properties=[ContractProperty(key='foo', value='6965')], description='foo') ) with open(current_app.config.get('PROJECT_ROOT') + '/purchasing_test/mock/award.html', 'r') as f: line_item_page = BeautifulSoup( f.read(), from_encoding='windows-1252' ) _line_items = grab_line_items(line_item_page) self.assertEquals(len(_line_items), 14) contract = get_contract( 'Sign Post, Square Tubes, Brackets, Etc.', 'IFB-6965' ) self.assertTrue(contract is not None) self.assertEquals(contract.id, new_contract.id) save_line_item(_line_items, contract) self.assertEquals(LineItem.query.count(), len(_line_items)) # assert that our ids made it in property for item in LineItem.query.all(): self.assertTrue(item.manufacturer is not None) self.assertTrue(item.model_number is not None) self.assertEquals(item.contract_id, contract.id) if 'muni' in item.company_name.lower(): self.assertEquals(item.company_id, muni.id) elif 'chem' in item.company_name.lower(): self.assertEquals(item.company_id, chemung.id) else: self.assertEquals(item.company_id, pathmaster.id)
def edit_company_contacts(contract_id): '''Update information about company contacts, and save all information New :py:class:`~purchasing.data.contracts.ContractBase` objects are created for each unique controller number. Notifications are also sent to all of the original contract's followers to say that the contract information has been replaced/updated with new info. :param contract_id: Primary key ID for a :py:class:`~purchasing.data.contracts.ContractBase` .. seealso:: * :py:class:`~purchasing.conductor.forms.CompanyContactListForm` * :py:meth:`~purchasing.data.contracts.ContractBase.create` * :py:meth:`~purchasing.data.contracts.ContractBase.complete` * :py:class:`~purchasing.notifications.Notification` :status 200: Render the CompanyContactListForm form :status 302: Post the data and redirect back to the success view, or redirect back to contract or company views if those haven't been completed yet. :status 404: Contract not found ''' contract = ContractBase.query.get(contract_id) if contract and session.get( 'contract-{}'.format(contract_id)) is not None and session.get( 'companies-{}'.format(contract_id)) is not None: form = CompanyContactListForm() # pull out companies from session, order them by financial id # so that they can be grouped appropriately companies = sorted(json.loads( session['companies-{}'.format(contract_id)]), key=lambda x: x.get('financial_id')) if form.validate_on_submit(): main_contract = contract for ix, _company in enumerate(companies): contract_data = json.loads( session['contract-{}'.format(contract_id)]) # because multiple companies can have the same name, don't use # get_or_create because it can create multiples if _company.get('company_id') > 0: company = Company.query.get(_company.get('company_id')) else: company = Company.create( company_name=_company.get('company_name')) # contacts should be unique to companies, though try: for _contact in form.data.get('companies')[ix].get( 'contacts'): _contact['company_id'] = company.id contact, _ = get_or_create(db.session, CompanyContact, **_contact) # if there are no contacts, an index error will be thrown for this company # so we catch it and just pass except IndexError: pass contract_data['financial_id'] = _company['financial_id'] if contract.financial_id is None or contract.financial_id == _company[ 'financial_id']: contract.update_with_spec_number(contract_data, company=company) else: contract = ContractBase.clone(contract, parent_id=contract.parent_id, strip=False) contract.update_with_spec_number(contract_data, company=company) contract.is_visible = True db.session.commit() Notification(to_email=[i.email for i in contract.followers], from_email=current_app.config['CONDUCTOR_SENDER'], reply_to=current_user.email, subject='A contract you follow has been updated!', html_template='conductor/emails/new_contract.html', contract=main_contract).send(multi=True) session.pop('contract-{}'.format(contract_id)) session.pop('companies-{}'.format(contract_id)) session['success-{}'.format(contract_id)] = True current_app.logger.info(''' CONDUCTOR CONTRACT COMPLETE - company contacts for contract "{}" assigned. |New contract(s) successfully created''' .format(contract.description)) if contract.parent: contract.parent.complete() return redirect( url_for('conductor.success', contract_id=main_contract.id)) if len(form.companies.entries) == 0: for company in companies: form.companies.append_entry() return render_template('conductor/edit/edit_company_contacts.html', form=form, contract=contract, companies=companies) elif session.get('contract-{}'.format(contract_id)) is None: return redirect(url_for('conductor.edit', contract_id=contract_id)) elif session.get('companies-{}'.format(contract_id)) is None: return redirect( url_for('conductor.edit_company', contract_id=contract_id)) abort(404)
def assign_a_contract(contract, flow, user, start_time=None, clone=True): '''Assign a contract a flow and a user If a flow is passed in, the following steps are performed: 1. If the ``clone`` flag is set to True, make a new cloned copy of the passed :py:class:`~purchasing.data.contracts.ContractBase` 2. Try to get or create the contract stages for the passed :py:class:`~purchasing.data.flows.Flow` 3. Inspect the output for the "revert" flag -- if the flag is set, that means that we are resetting the entry time on the first stage. In order to do this, we clear out the current stage information, which will make the contract transition to the first stage of it's flow 4. If that raises an error, it is because something went wrong with the creation/getting of the contract stages 5. Assign the contract's flow to the passed :py:class:`~purchasing.data.flows.Flow` and its assigned user to the passed :py:class:`~purchasing.users.models.User` and commit See Also: * :py:meth:`~purchasing.data.flows.Flow.create_contract_stages` for how stages are gotten/created * :py:meth:`~purchasing.data.contracts.ContractBase.transition` for how the contract handles transitioning into different stages Arguments: contract: A :py:class:`~purchasing.data.contracts.ContractBase` object to assign flow: A :py:class:`~purchasing.data.flows.Flow` object to assign user: A :py:class:`~purchasing.users.models.User` object to assign Keyword Arguments: start_time: An optional start time for starting work on the :py:class:`~purchasing.data.contracts.ContractBase` when it starts its first :py:class:`~purchasing.data.contract_stages.ContractStage` clone: A boolean flag of whether or not to make a clone of the passed :py:class:`~purchasing.data.contracts.ContractBase` Returns: An assigned :py:class:`~purchasing.data.contracts.ContractBase` if we are given a flow, False otherwise ''' # if we don't have a flow, stop if not flow: return False if clone: contract = ContractBase.clone(contract) db.session.add(contract) db.session.commit() try: stages, _, revert = flow.create_contract_stages(contract) if revert and start_time and contract.get_first_stage().id == contract.current_stage_id: contract.current_stage = None contract.current_stage_id = None actions = contract.transition(user, complete_time=start_time) for i in actions: db.session.add(i) db.session.flush() except IntegrityError: # we already have the sequence for this, so just # rollback and pass db.session.rollback() pass contract.flow = flow contract.assigned_to = user.id db.session.commit() current_app.logger.info('CONDUCTOR ASSIGN - new contract "{}" assigned to {} with flow {}'.format( contract.description, contract.assigned.email, contract.flow.flow_name )) return contract
def start_work(contract_id=-1): '''Start work on a contract When new work is started on a contract, a new contract is created (either as a clone of an existing contract, or as brand new work), assigned to the :py:class:`~purchasing.data.flows.Flow` and :py:class:`~purchasing.users.models.User` indicated in the :py:class:`~purchasing.conductor.forms.NewContractForm` :param contract_id: Primary key ID for a :py:class:`~purchasing.data.contracts.ContractBase` .. seealso:: :py:meth:`~purchasing.data.contracts.ContractBase.clone` for more information on how new contracts get started, and :py:class:`~purchasing.conductor.forms.NewContractForm` for more on the form to start new work. :status 200: Render the new contract view :status 302: Create or start new work on a cloned contract ''' contract = ContractBase.query.get(contract_id) if contract: first_stage = contract.get_first_stage() if first_stage and first_stage.stage_id != contract.current_stage_id: return redirect( url_for('conductor.detail', contract_id=contract.id)) elif first_stage: contract.start = localize_datetime( contract.get_first_stage().entered) else: contract = ContractBase() form = NewContractForm(obj=contract) if form.validate_on_submit(): if contract_id == -1: contract, _ = get_or_create( db.session, ContractBase, description=form.data.get('description'), department=form.data.get('department'), is_visible=False) elif not first_stage: contract = ContractBase.clone(contract) contract.description = form.data.get('description') contract.department = form.data.get('department') db.session.add(contract) db.session.commit() assigned = assign_a_contract( contract, form.data.get('flow'), form.data.get('assigned'), start_time=form.data.get('start').astimezone( pytz.UTC).replace(tzinfo=None), clone=False) db.session.commit() if assigned: flash( 'Successfully assigned {} to {}!'.format( assigned.description, assigned.assigned.email), 'alert-success') return redirect( url_for('conductor.detail', contract_id=assigned.id)) else: flash("That flow doesn't exist!", 'alert-danger') return render_template('conductor/new.html', form=form, contract_id=contract_id, contract=contract)
def edit_company_contacts(contract_id): '''Update information about company contacts, and save all information New :py:class:`~purchasing.data.contracts.ContractBase` objects are created for each unique controller number. Notifications are also sent to all of the original contract's followers to say that the contract information has been replaced/updated with new info. :param contract_id: Primary key ID for a :py:class:`~purchasing.data.contracts.ContractBase` .. seealso:: * :py:class:`~purchasing.conductor.forms.CompanyContactListForm` * :py:meth:`~purchasing.data.contracts.ContractBase.create` * :py:meth:`~purchasing.data.contracts.ContractBase.complete` * :py:class:`~purchasing.notifications.Notification` :status 200: Render the CompanyContactListForm form :status 302: Post the data and redirect back to the success view, or redirect back to contract or company views if those haven't been completed yet. :status 404: Contract not found ''' contract = ContractBase.query.get(contract_id) if contract and session.get('contract-{}'.format(contract_id)) is not None and session.get('companies-{}'.format(contract_id)) is not None: form = CompanyContactListForm() # pull out companies from session, order them by financial id # so that they can be grouped appropriately companies = sorted( json.loads(session['companies-{}'.format(contract_id)]), key=lambda x: x.get('financial_id') ) if form.validate_on_submit(): main_contract = contract for ix, _company in enumerate(companies): contract_data = json.loads(session['contract-{}'.format(contract_id)]) # because multiple companies can have the same name, don't use # get_or_create because it can create multiples if _company.get('company_id') > 0: company = Company.query.get(_company.get('company_id')) else: company = Company.create(company_name=_company.get('company_name')) # contacts should be unique to companies, though try: for _contact in form.data.get('companies')[ix].get('contacts'): _contact['company_id'] = company.id contact, _ = get_or_create(db.session, CompanyContact, **_contact) # if there are no contacts, an index error will be thrown for this company # so we catch it and just pass except IndexError: pass contract_data['financial_id'] = _company['financial_id'] if contract.financial_id is None or contract.financial_id == _company['financial_id']: contract.update_with_spec_number(contract_data, company=company) else: contract = ContractBase.clone(contract, parent_id=contract.parent_id, strip=False) contract.update_with_spec_number(contract_data, company=company) contract.is_visible = True db.session.commit() Notification( to_email=[i.email for i in contract.followers], from_email=current_app.config['CONDUCTOR_SENDER'], reply_to=current_user.email, subject='A contract you follow has been updated!', html_template='conductor/emails/new_contract.html', contract=main_contract ).send(multi=True) session.pop('contract-{}'.format(contract_id)) session.pop('companies-{}'.format(contract_id)) session['success-{}'.format(contract_id)] = True current_app.logger.info(''' CONDUCTOR CONTRACT COMPLETE - company contacts for contract "{}" assigned. |New contract(s) successfully created'''.format( contract.description )) if contract.parent: contract.parent.complete() return redirect(url_for('conductor.success', contract_id=main_contract.id)) if len(form.companies.entries) == 0: for company in companies: form.companies.append_entry() return render_template( 'conductor/edit/edit_company_contacts.html', form=form, contract=contract, companies=companies ) elif session.get('contract-{}'.format(contract_id)) is None: return redirect(url_for('conductor.edit', contract_id=contract_id)) elif session.get('companies-{}'.format(contract_id)) is None: return redirect(url_for('conductor.edit_company', contract_id=contract_id)) abort(404)
def start_work(contract_id=-1): '''Start work on a contract When new work is started on a contract, a new contract is created (either as a clone of an existing contract, or as brand new work), assigned to the :py:class:`~purchasing.data.flows.Flow` and :py:class:`~purchasing.users.models.User` indicated in the :py:class:`~purchasing.conductor.forms.NewContractForm` :param contract_id: Primary key ID for a :py:class:`~purchasing.data.contracts.ContractBase` .. seealso:: :py:meth:`~purchasing.data.contracts.ContractBase.clone` for more information on how new contracts get started, and :py:class:`~purchasing.conductor.forms.NewContractForm` for more on the form to start new work. :status 200: Render the new contract view :status 302: Create or start new work on a cloned contract ''' contract = ContractBase.query.get(contract_id) if contract: first_stage = contract.get_first_stage() if first_stage and contract.completed_last_stage(): pass elif first_stage and first_stage.stage_id != contract.current_stage_id: return redirect(url_for('conductor.detail', contract_id=contract.id)) elif first_stage: contract.start = localize_datetime(contract.get_first_stage().entered) else: contract = ContractBase() form = NewContractForm(obj=contract) if form.validate_on_submit(): if contract_id == -1: contract, _ = get_or_create( db.session, ContractBase, description=form.data.get('description'), department=form.data.get('department'), is_visible=False ) elif not first_stage or contract.completed_last_stage(): contract = ContractBase.clone(contract) contract.description = form.data.get('description') contract.department = form.data.get('department') db.session.add(contract) db.session.commit() assigned = assign_a_contract( contract, form.data.get('flow'), form.data.get('assigned'), start_time=form.data.get('start').astimezone(pytz.UTC).replace(tzinfo=None), clone=False ) db.session.commit() if assigned: flash('Successfully assigned {} to {}!'.format(assigned.description, assigned.assigned.email), 'alert-success') return redirect(url_for('conductor.detail', contract_id=assigned.id)) else: flash("That flow doesn't exist!", 'alert-danger') return render_template('conductor/new.html', form=form, contract_id=contract_id, contract=contract)
def edit_company_contacts(contract_id): contract = ContractBase.query.get(contract_id) if contract and session.get('contract') is not None and session.get('companies') is not None: form = CompanyContactListForm() companies = json.loads(session['companies']) contract_data = json.loads(session['contract']) if form.validate_on_submit(): main_contract = contract for ix, _company in enumerate(companies): # because multiple companies can have the same name, don't use # get_or_create because it can create multiples if _company.get('company_id') > 0: company = Company.query.get(_company.get('company_id')) else: company = Company.create(company_name=_company.get('company_name')) # contacts should be unique to companies, though for _contact in form.data.get('companies')[ix].get('contacts'): _contact['company_id'] = company.id contact, _ = get_or_create(db.session, CompanyContact, **_contact) contract_data['financial_id'] = _company['financial_id'] if ix == 0: contract.update_with_spec_number(contract_data, company=company) else: contract = ContractBase.clone(contract, parent_id=contract.parent_id, strip=False) contract.update_with_spec_number(contract_data, company=company) contract.is_visible = True contract.parent.is_archived = True if not contract.parent.description.endswith('[Archived]'): contract.parent.description += ' [Archived]' db.session.commit() Notification( to_email=[i.email for i in contract.followers], from_email=current_app.config['CONDUCTOR_SENDER'], reply_to=current_user.email, subject='A contract you follow has been updated!', html_template='conductor/emails/new_contract.html', contract=main_contract ).send(multi=True) session.pop('contract') session.pop('companies') session['success'] = True current_app.logger.info(''' CONDUCTOR CONTRACT COMPLETE - company contacts for contract "{}" assigned. New contract(s) successfully created'''.format( contract.description )) return redirect(url_for('conductor.success', contract_id=main_contract.id)) if len(form.companies.entries) == 0: for company in companies: form.companies.append_entry() return render_template( 'conductor/edit/edit_company_contacts.html', form=form, contract=contract, companies=companies ) elif session.get('contract') is None: return redirect(url_for('conductor.edit', contract_id=contract_id)) elif session.get('companies') is None: return redirect(url_for('conductor.edit_company', contract_id=contract_id)) abort(404)
def assign_a_contract(contract, flow, user, start_time=None, clone=True): '''Assign a contract a flow and a user If a flow is passed in, the following steps are performed: 1. If the ``clone`` flag is set to True, make a new cloned copy of the passed :py:class:`~purchasing.data.contracts.ContractBase` 2. Try to get or create the contract stages for the passed :py:class:`~purchasing.data.flows.Flow` 3. Inspect the output for the "revert" flag -- if the flag is set, that means that we are resetting the entry time on the first stage. In order to do this, we clear out the current stage information, which will make the contract transition to the first stage of it's flow 4. If that raises an error, it is because something went wrong with the creation/getting of the contract stages 5. Assign the contract's flow to the passed :py:class:`~purchasing.data.flows.Flow` and its assigned user to the passed :py:class:`~purchasing.users.models.User` and commit See Also: * :py:meth:`~purchasing.data.flows.Flow.create_contract_stages` for how stages are gotten/created * :py:meth:`~purchasing.data.contracts.ContractBase.transition` for how the contract handles transitioning into different stages Arguments: contract: A :py:class:`~purchasing.data.contracts.ContractBase` object to assign flow: A :py:class:`~purchasing.data.flows.Flow` object to assign user: A :py:class:`~purchasing.users.models.User` object to assign Keyword Arguments: start_time: An optional start time for starting work on the :py:class:`~purchasing.data.contracts.ContractBase` when it starts its first :py:class:`~purchasing.data.contract_stages.ContractStage` clone: A boolean flag of whether or not to make a clone of the passed :py:class:`~purchasing.data.contracts.ContractBase` Returns: An assigned :py:class:`~purchasing.data.contracts.ContractBase` if we are given a flow, False otherwise ''' # if we don't have a flow, stop if not flow: return False if clone: contract = ContractBase.clone(contract) db.session.add(contract) db.session.commit() try: stages, _, revert = flow.create_contract_stages(contract) if revert and start_time and contract.get_first_stage( ).id == contract.current_stage_id: contract.current_stage = None contract.current_stage_id = None actions = contract.transition(user, complete_time=start_time) for i in actions: db.session.add(i) db.session.flush() except IntegrityError: # we already have the sequence for this, so just # rollback and pass db.session.rollback() pass contract.flow = flow contract.assigned_to = user.id db.session.commit() current_app.logger.info( 'CONDUCTOR ASSIGN - new contract "{}" assigned to {} with flow {}'. format(contract.description, contract.assigned.email, contract.flow.flow_name)) return contract