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 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 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
Beispiel #5
0
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