Пример #1
0
class Property(ModelSQL, ModelView):
    _name = 'ir.property'
    company = fields.Many2One('company.company',
                              'Company',
                              domain=[
                                  ('id',
                                   If(In('company', Eval('context', {})), '=',
                                      '!='),
                                   Get(Eval('context', {}), 'company', 0)),
                              ])

    def _set_values(self, name, model, res_id, val, field_id):
        user_obj = self.pool.get('res.user')
        user = user_obj.browse(Transaction().user)
        res = super(Property, self)._set_values(name, model, res_id, val,
                                                field_id)
        if user:
            res['company'] = user.company.id
        return res

    def search(self, domain, offset=0, limit=None, order=None, count=False):
        if Transaction().user == 0:
            domain = ['AND', domain[:], ('company', '=', False)]
        return super(Property, self).search(domain,
                                            offset=offset,
                                            limit=limit,
                                            order=order,
                                            count=count)
Пример #2
0
class ProductionLeadTime(sequence_ordered(), ModelSQL, ModelView, MatchMixin):
    'Production Lead Time'
    __name__ = 'production.lead_time'

    product = fields.Many2One('product.product',
                              'Product',
                              ondelete='CASCADE',
                              select=True,
                              required=True,
                              domain=[
                                  ('type', '!=', 'service'),
                              ])
    bom = fields.Many2One('production.bom',
                          'BOM',
                          ondelete='CASCADE',
                          domain=[
                              ('output_products', '=',
                               If(Bool(Eval('product')), Eval('product', -1),
                                  Get(Eval('_parent_product', {}), 'id', 0))),
                          ],
                          depends=['product'])
    lead_time = fields.TimeDelta('Lead Time')

    @classmethod
    def __setup__(cls):
        super(ProductionLeadTime, cls).__setup__()
        cls._order.insert(0, ('product', 'ASC'))
Пример #3
0
class ProductBom(sequence_ordered(), ModelSQL, ModelView):
    'Product - BOM'
    __name__ = 'product.product-production.bom'

    product = fields.Many2One('product.product',
                              'Product',
                              ondelete='CASCADE',
                              select=1,
                              required=True,
                              domain=[
                                  ('producible', '=', True),
                              ])
    bom = fields.Many2One('production.bom',
                          'BOM',
                          ondelete='CASCADE',
                          select=1,
                          required=True,
                          domain=[
                              ('output_products', '=',
                               If(Bool(Eval('product')), Eval('product', 0),
                                  Get(Eval('_parent_product', {}), 'id', 0))),
                          ],
                          depends=['product'])

    def get_rec_name(self, name):
        return self.bom.rec_name

    @classmethod
    def search_rec_name(cls, name, clause):
        return [('bom.rec_name', ) + tuple(clause[1:])]
Пример #4
0
    def __setup__(cls):
        super().__setup__()
        cls._order.insert(0, ('rec_name', 'ASC'))

        types_cost_method = cls._cost_price_method_domain_per_type()
        cls.cost_price_method.domain = [
            Get(types_cost_method, Eval('type'), []),
        ]
Пример #5
0
 def view_attributes(cls):
     return super(SaleLine, cls).view_attributes() + [
         ('//page[@id="gift_cards"]', 'states', {
             'invisible': ~Bool(Eval('is_gift_card'))
         }), ('//separator[@id="recipient_details"]', 'states', {
             'invisible': ~Bool(Eval('is_gift_card'))
         }), (
             '//field[@name="message"]', 'spell',
             Get(Eval('_parent_sale', {}), 'party_lang')
         )]
Пример #6
0
class Configuration:
    __name__ = 'account.configuration'
    stock_journal = fields.Property(fields.Many2One(
            'account.journal', 'Stock Journal',
            states={
                'required': Bool(Eval('context', {}).get('company')),
                }))
    cost_price_counterpart_account = fields.Property(fields.Many2One(
            'account.account', 'Cost Price Counterpart Account', domain=[
                ('company', 'in', [Get(Eval('context', {}), 'company'), None]),
                ]))
Пример #7
0
class InvoiceLine(ModelSQL, ModelView):
    """Invoice Line"""
    _name = 'account.invoice.line'

    work = fields.Many2One('timesheet.work', 'Work')
    timesheet_lines = fields.One2Many(
        'timesheet.line',
        'invoice_line',
        'Timesheet Lines',
        add_remove=[
            ('invoice_line', '=', False),
            ('work', '=', Eval('work')),
            ('billable', '=', True),
            ('hours', '>', 0),
        ],
    )

    # add timesheet_lines to on_change_with list on parent
    quantity = fields.Float('Quantity',
                            digits=(16, Eval('unit_digits', 2)),
                            states={
                                'invisible': Not(Equal(Eval('type'), 'line')),
                                'required': Equal(Eval('type'), 'line'),
                            },
                            on_change_with=['timesheet_lines'])

    # add timesheet_lines to on_change_with list on parent
    amount = fields.Function(
        fields.Numeric('Amount',
                       digits=(16,
                               Get(Eval('_parent_invoice',
                                        {}), 'currency_digits',
                                   Eval('currency_digits', 2))),
                       states={
                           'invisible':
                           Not(In(Eval('type'), ['line', 'subtotal'])),
                       },
                       on_change_with=[
                           'type',
                           'quantity',
                           'unit_price',
                           '_parent_invoice.currency',
                           'currency',
                           'timesheet_lines',
                       ]), 'get_amount')

    def on_change_with_quantity(self, vals):
        hours = 0.0
        for line in vals.get('timesheet_lines'):
            hours = hours + line.get('hours')
        return hours
Пример #8
0
class Configuration:
    __metaclass__ = PoolMeta
    __name__ = 'account.configuration'
    stock_journal = fields.MultiValue(stock_journal)
    cost_price_counterpart_account = fields.MultiValue(
        fields.Many2One('account.account',
                        "Cost Price Counterpart Account",
                        domain=[
                            ('company', 'in',
                             [Get(Eval('context', {}), 'company'), None]),
                        ]))

    @classmethod
    def default_stock_journal(cls, **pattern):
        return cls.multivalue_model('stock_journal').default_stock_journal()
Пример #9
0
class Sequence(ModelSQL, ModelView):
    _name = 'ir.sequence'
    company = fields.Many2One('company.company',
                              'Company',
                              domain=[
                                  ('id',
                                   If(In('company', Eval('context', {})), '=',
                                      '!='),
                                   Get(Eval('context', {}), 'company', 0)),
                              ])

    def __init__(self):
        super(Sequence, self).__init__()
        self._order.insert(0, ('company', 'ASC'))

    def default_company(self):
        return Transaction().context.get('company') or False
Пример #10
0
class ProductBom(ModelSQL, ModelView):
    'Product - BOM'
    __name__ = 'product.product-production.bom'

    product = fields.Many2One('product.product',
                              'Product',
                              ondelete='CASCADE',
                              select=1,
                              required=True,
                              domain=[
                                  ('type', '!=', 'service'),
                              ])
    bom = fields.Many2One('production.bom',
                          'BOM',
                          ondelete='CASCADE',
                          select=1,
                          required=True,
                          domain=[
                              ('output_products', '=',
                               If(Bool(Eval('product')), Eval('product', 0),
                                  Get(Eval('_parent_product', {}), 'id', 0))),
                          ],
                          depends=['product'])
    sequence = fields.Integer('Sequence')

    @classmethod
    def __setup__(cls):
        super(ProductBom, cls).__setup__()
        cls._order.insert(0, ('sequence', 'ASC'))

    def get_rec_name(self, name):
        return self.bom.rec_name

    @classmethod
    def search_rec_name(cls, name, clause):
        return [('bom.rec_name', ) + tuple(clause[1:])]

    @staticmethod
    def order_sequence(tables):
        table, _ = tables[None]
        return [Case((table.sequence == Null, 0), else_=1), table.sequence]
Пример #11
0
class DocumentRequest(ModelSQL, ModelView):
    "Documents of request"
    _name = 'ekd.document.head.request'
    _description = __doc__
    _inherits = {'ekd.document': 'document'}

    document = fields.Many2One('ekd.document',
                               'Document',
                               required=True,
                               ondelete='CASCADE')

    template_request = fields.Function(fields.Many2One(
        'ekd.document.template',
        'Document',
        help="Template document",
        states=_REQUEST_STATES,
        depends=_REQUEST_DEPENDS,
        domain=[('type_account', '=',
                 Eval('type_request', Get(Eval('context', {}),
                                          'type_request')))]),
                                       'get_fields',
                                       setter='set_fields')

    date_issued = fields.Function(fields.Date('Date of issue',
                                              states=_ISSUE_STATES,
                                              depends=_REQUEST_DEPENDS),
                                  'get_fields',
                                  setter='set_fields')
    type_request = fields.Selection([
        ('request_monies', 'Money'),
        ('request_product', 'Product'),
        ('request_service', 'Service'),
    ],
                                    required=True,
                                    string='Type request')

    document_ref = fields.Reference(
        'Base',
        selection='documents_get',
        select=1,
        domain=[('state', '=', 'open')],
        context={'request_from': 'requestcash'},
        states=_REQUEST_STATES,
        depends=_REQUEST_DEPENDS,
        on_change=['document_ref'],
    )
    document_name = fields.Function(fields.Char('Document Name'),
                                    'get_doc_name')
    invoice_ref = fields.Reference('Invoice',
                                   selection='invoice_get',
                                   select=1,
                                   states=_REQUEST_STATES,
                                   depends=_REQUEST_DEPENDS)
    manager = fields.Function(fields.Many2One('company.employee',
                                              'Manager to confirm',
                                              states=_REQUEST_STATES,
                                              depends=_REQUEST_DEPENDS),
                              'get_fields',
                              setter='set_fields')
    recipient = fields.Function(fields.Many2One('party.party',
                                                'Recipient',
                                                states=_REQUEST_STATES,
                                                depends=_REQUEST_DEPENDS),
                                'get_fields',
                                setter='set_fields')

    amount_request = fields.Function(fields.Numeric(
        'Total Request',
        digits=(16, Eval('currency_digits', 2)),
        readonly=True,
        on_change_with=['lines']),
                                     'get_fields',
                                     setter='set_fields')
    amount_received = fields.Function(fields.Numeric(
        'Total Received',
        digits=(16, Eval('currency_digits', 2)),
        readonly=True,
        on_change_with=['lines']),
                                      'get_paid_fields',
                                      setter='set_fields')
    amount_balance = fields.Function(
        fields.Numeric('Balance',
                       digits=(16, Eval('currency_digits', 2)),
                       readonly=True,
                       on_change_with=['lines']), 'get_fields')
    lines = fields.One2Many('ekd.document.line.request',
                            'requestcash',
                            'Lines Request',
                            states=_RECEIVED_STATES,
                            depends=_RECEIVED_DEPENDS,
                            context={
                                'document_ref': Eval('document_ref'),
                                'currency_digits': Eval('currency_digits')
                            })

    #    currency = fields.Many2One('currency.currency', 'Currency')
    currency_digits = fields.Function(
        fields.Integer('Currency Digits', on_change_with=['currency']),
        'get_currency_digits')

    state_doc = fields.Function(fields.Selection(_STATE_REQUEST,
                                                 required=True,
                                                 readonly=True,
                                                 string='State'),
                                'get_fields',
                                setter='set_fields')

    # Инициализация класса
    def __init__(self):
        super(DocumentRequest, self).__init__()
        self._rpc.update({
            'button_add_number': True,
            'button_draft': True,
            'button_request': True,
            'button_cancel': True,
            'button_issue': True,
            'button_restore': True,
            'button_confirmed': True,
            'button_payment': True,
        })
        self._order.insert(0, ('date_document', 'ASC'))
        self._order.insert(1, ('number_our', 'ASC'))

        self._error_messages.update({
            'you_dont_confirm':
            'You can not confirm this request on money!',
            'manager_not_find':
            "Manager as User don't found",
            'employee_not_find':
            "Employee as User don't found",
        })
#
#	Заполнение полей значениями по умолчанию
#

    def default_state_doc(self):
        return 'empty'

    def default_type_request(self):
        return 'request_monies'

    def default_date_issued(self):
        return Transaction().context.get('current_date',
                                         datetime.datetime.now())

    def default_employee(self):
        return Transaction().context.get('employee', False)

    def default_recipient(self):
        return Transaction().context.get('employee', False)

#    def default_currency(self):
#        company_obj = self.pool.get('company.company')
#        currency_obj = self.pool.get('currency.currency')
#        if context is None:
#            context = {}
#        if context.get('company'):
#            company = company_obj.browse(context['company'],
#                    )
#            return company.currency.id
#        return False

#    def default_currency_digits(self):
#        company_obj = self.pool.get('company.company')
#        if context is None:
#            context = {}
#        if context.get('company'):
#            company = company_obj.browse(context['company'],
#                    )
#            return company.currency.digits
#        return 2

################################################################################
#
# Процедуры заполнения функциональных полей
#
################################################################################

    def get_rec_name(self, ids, name):
        if not ids:
            return {}
        return self.pool.get('ekd.document').get_rec_name(ids, name)

    def get_template_select(self, ids, name):
        template_obj = self.pool.get('ekd.document.template')
        template_ids = template_obj.search(
            ['type_account', '=', 'request_monies'])
        res = []
        for template in template_obj.browse(template_ids):
            res.append([template.id, template.name])
        return res

    def get_currency_digits(self, ids, name):
        assert name in ('currency_digits'), 'Invalid name %s' % (name)
        res = {}.fromkeys(ids, 2)
        for document in self.browse(ids):
            if document.currency:
                res[document.id] = document.currency.digits
        return res

    def get_amount_line(self, ids, names):
        if not ids:
            return {}
        res = {}
        amount_request = Decimal('0.0')
        amount_received = Decimal('0.0')
        for request in self.browse(ids):
            for line in request.lines:
                amount_request += abs(line.amount_request)
                amount_received += abs(line.amount_received)
            for name in names:
                res.setdefault(name, {})
                res[name].setdefault(request.id, False)
                if name == 'amount_request':
                    res[name][request.id] = amount_request
                elif name == 'amount_received':
                    res[name][request.id] = amount_received
                elif name == 'amount_balance':
                    res[name][request.id] = amount_request - amount_received
            amount_request = Decimal('0.0')
            amount_received = Decimal('0.0')
        return res

    def get_doc_name(self, ids, name):
        if not ids:
            return
        res = {}
        for request in self.browse(ids):
            if request.document_ref:
                model, model_id = request.document_ref.split(',')
                if model and model_id:
                    model_rec = self.pool.get(model).browse(int(model_id))
                    if model_rec.full_name:
                        res[request.id] = model_rec.full_name
                    elif model_rec.rec_name:
                        res[request.id] = model_rec.rec_name
                    elif model_rec.name:
                        res[request.id] = model_rec.name
                    else:
                        res[request.id] = 'name null'
        return res

    def documents_get(self):
        dictions_obj = self.pool.get('ir.dictions')
        res = []
        diction_ids = dictions_obj.search([
            ('model', '=', 'ekd.document.head.request'),
            ('pole', '=', 'document_ref'),
        ])
        for diction in dictions_obj.browse(diction_ids):
            res.append([diction.key, diction.value])
        return res

    def get_documents_ref(ids, model, name, values=None):
        raise Exception('get_documents_ref')

    def invoice_get(self):
        dictions_obj = self.pool.get('ir.dictions')
        res = []
        diction_ids = dictions_obj.search([
            ('model', '=', 'ekd.document.head.request'),
            ('pole', '=', 'invoice_ref'),
        ])
        for diction in dictions_obj.browse(diction_ids):
            res.append([diction.key, diction.value])
        return res

    def get_fields(self, ids, names):
        if not ids:
            return {}
        res = {}
        for request in self.browse(ids):
            for name in names:
                res.setdefault(name, {})
                res[name].setdefault(request.id, False)
                if name == 'manager':
                    res[name][request.id] = request.from_party.id
                elif name == 'employee_':
                    res[name][request.id] = request.employee.id
                elif name == 'recipient':
                    res[name][request.id] = request.to_party.id
                elif name == 'date_issued':
                    res[name][request.id] = request.date_account
                elif name == 'state_doc':
                    res[name][request.id] = request.state
                elif name == 'template_request':
                    res[name][request.id] = request.template.id
                elif name == 'amount_request':
                    res[name][request.id] = request.amount
#                elif name == 'amount_received':
#                    res[name][request.id] = request.amount_payment
                elif name == 'amount_balance':
                    res[name][
                        request.
                        id] = request.amount - request.amount_payment - request.amount_received

        return res

    def set_fields(self, id, name, value):
        #        assert name in ('manager', 'employee', 'date_issued', 'state_doc'), 'Invalid name SET PARTY %s' % (name)
        if not value:
            return
        if name == 'manager':
            self.write(id, {
                'from_party': value,
            })
        elif name == 'employee':
            self.write(id, {
                'employee': value,
            })
        elif name == 'recipient':
            self.write(id, {
                'to_party': value,
            })
        elif name == 'date_issued':
            self.write(id, {
                'date_account': value,
            })
        elif name == 'state_doc':
            self.write(id, {
                'state': value,
            })
        elif name == 'amount_request':
            self.write(id, {
                'amount': value,
            })
        elif name == 'amount_received':
            self.write(id, {
                'amount_pay': value,
            })
        elif name == 'template_request':
            self.write(id, {
                'template': value,
            })
        elif name == 'amount_request':
            self.write(id, {
                'amount': value,
            })

    def get_paid_fields(self, ids, name):
        res = {}.fromkeys(ids, Decimal('0.0'))
        cr = Transaction().cursor
        cr.execute("SELECT doc_base, SUM(amount_payment) "\
                    "FROM ekd_document_line_payment "\
                    "WHERE doc_base in ("+','.join(map(str,ids))+") and state='done' "\
                    "GROUP BY doc_base")
        for id, sum in cr.fetchall():
            # SQLite uses float for SUM
            if not isinstance(sum, Decimal):
                sum = Decimal(str(sum))
            res[id] = sum
        return res

################################################################################
#
# События изменения полей
#
################################################################################

    def on_change_with_amount_request(self, values):
        amount = Decimal('0.0')
        for line in values['lines']:
            amount += line['amount_request']
        return amount

    def on_change_with_amount_received(self, values):
        amount = Decimal('0.0')
        for line in values['lines']:
            amount += line['amount_received']
        return amount

    def on_change_with_amount_balance(self, values):
        amount = Decimal('0.0')
        for line in values['lines']:
            amount += abs(line['amount_request']) - abs(
                line['amount_received'])
        return amount

    def on_change_document_ref(self, value):
        res = {}
        if value.get('document_ref'):
            model, model_id = value.get('document_ref').split(',')
            if model_id == '0':
                return res
            if model == 'project.project':
                model_obj = self.pool.get(model)
                model_ids = model_obj.browse(int(model_id))
                if model_ids.manager:
                    res['manager'] = model_ids.manager.id
                if model_ids.employee:
                    res['recepient'] = model_ids.employee.id
                if model_ids.company:
                    res['company'] = model_ids.company.id
        return res


#
#	Обработка клиентских вызовов процедур
#

    def button_draft(self, ids):
        return self.draft(ids)

    def button_confirmed(self, ids):
        return self.confirmed(ids)

    def button_cancel(self, ids):
        return self.draft(ids)

    def button_issue(self, ids):
        return self.paid(ids)

    def button_restore(self, ids):
        return self.draft(ids)

    def button_request(self, ids):
        return self.request(ids)

    def button_payment(self, ids):
        return self.on_payment(ids)

    def button_add_number(self, ids):
        return self.new_number(ids)

    def new_number(self, ids):
        sequence_obj = self.pool.get('ir.sequence')
        for document in self.browse(ids):
            if document.template and document.template.sequence:
                sequence = sequence_obj.get_id(document.template.sequence.id)
            else:
                raise Exception('Error', 'Sequence for document not find!')
            self.write(document.id, {
                'number_our': sequence,
            })

    # Изменение состояния документа в Черновик
    def draft(self, ids):
        requestline_obj = self.pool.get('ekd.document.line.request')
        for line in self.browse(ids):
            requestline_obj.write([k.id for k in line.lines], {
                'state': 'draft',
            })
            self.write(ids, {
                'state': 'draft',
            })

    # Закрытие документа
    def done(self, ids):
        requestline_obj = self.pool.get('ekd.document.line.request')
        for line in self.browse(ids):
            requestline_obj.write([k.id for k in line.lines], {
                'state': 'done',
            })
            self.write(ids, {
                'state': 'done',
            })

    # Формирование запроса для подтверждения документа
    def request(self, ids):
        sequence_obj = self.pool.get('ir.sequence')
        date_obj = self.pool.get('ir.date')
        request_obj = self.pool.get('res.request')
        req_ref_obj = self.pool.get('res.request.reference')
        user_obj = self.pool.get('res.user')
        requestline_obj = self.pool.get('ekd.document.line.request')
        for requestcash in self.browse(ids):
            if requestcash.manager:
                reference = sequence_obj.get_id(
                    requestcash.template.sequence.id)
                for line in self.browse(ids):
                    requestline_obj.write([k.id for k in line.lines], {
                        'state': 'request',
                    })
                self.write(
                    requestcash.id, {
                        'number_our': reference,
                        'state': 'request',
                        'post_date': date_obj.today(),
                    })
                manager_id = user_obj.search(
                    [('employee', '=', requestcash.manager.id)], limit=1)

                employee_id = user_obj.search(
                    [('employee', '=', requestcash.employee.id)], limit=1)

                if len(manager_id) == 0:
                    self.raise_user_error(cursor,
                                          error="Warning",
                                          error_description="manager_not_find")
                if len(employee_id) == 0:
                    self.raise_user_error(
                        cursor,
                        error="Warning",
                        error_description="employee_not_find")

                if isinstance(manager_id, list):
                    manager_id = manager_id[0]

                if isinstance(employee_id, list):
                    employee_id = employee_id[0]

                vals = {}
                vals['name'] = u"Пожалуйста подтвердите"\
                    u" - %s с датой выдачи %s"%(requestcash.rec_name,
                                               requestcash.date_issued.strftime('%d.%m.%Y'))

                vals['active'] = True
                vals['priority'] = '1'
                vals['act_from'] = employee_id
                vals['act_to'] = manager_id
                vals['body'] = requestcash.note
                vals['state'] = 'waiting'

                request_id = request_obj.create(vals)

                req_ref_obj.create({
                    'request':
                    request_id,
                    'reference':
                    '%s,%s' % ('ekd.document.head.request', ids[0]),
                })

    # Подтверждение документа
    def confirmed(self, requestcash_id):
        sequence_obj = self.pool.get('ir.sequence')
        date_obj = self.pool.get('ir.date')
        requestline_obj = self.pool.get('ekd.document.line.request')
        for document in self.browse(requestcash_id):
            reference = sequence_obj.get_id(document.template.sequence.id)
            if document.from_party.id == Transaction().context.get(
                    'employee', False):
                requestline_obj.write([k.id for k in document.lines], {
                    'state': 'confirmed',
                })
                self.write(
                    document.id, {
                        'number_our': reference,
                        'state': 'confirmed',
                        'post_date': date_obj.today(),
                    })
            else:
                self.raise_user_error(error="Error",
                                      error_description="you_dont_confirm")
        return

    # Постановка на оплату
    def on_payment(self, ids):
        requestline_obj = self.pool.get('ekd.document.line.request')
        for line in self.browse(ids):
            requestline_obj.write([k.id for k in line.lines], {
                'state': 'payment',
            })
        self.write(ids, {
            'state': 'payment',
        })

    def paid(self, ids):
        self.write(ids, {
            'state': 'paid',
        })

    # Создание объекта
    def create(self, vals):
        later = {}
        vals = vals.copy()
        cr = Transaction().cursor
        for field in vals:
            if field in self._columns\
                        and hasattr(self._columns[field], 'set'):
                later[field] = vals[field]
        for field in later:
            del vals[field]
        if vals.get('invoice_ref') == ',':
            del vals['invoice_ref']
        if vals.get('document_ref') == ',':
            del vals['invoice_ref']
        if cr.nextid(self._table):
            cr.setnextid(self._table, cr.currid(self._table))
        new_id = super(DocumentRequest, self).create(vals)
        request = self.browse(new_id)
        new_id = request.document.id
        cr.execute('UPDATE "' + self._table + '" SET id = %s '\
                        'WHERE id = %s', (request.document.id, request.id))
        ModelStorage.delete(self, request.id)
        self.write(new_id, later)
        res = self.browse(new_id)
        return res.id
Пример #12
0
class DocumentInvoiceGoods(ModelWorkflow, ModelSQL, ModelView):
    "Documents (Invoice) "
    _name='ekd.document.head.invoice_goods'
    _description=__doc__
    _inherits = {'ekd.document': 'document'}

    document = fields.Many2One('ekd.document', 'Document', required=True,
            ondelete='CASCADE')

    direct_document = fields.Selection([
                ('output_goods', 'For Customer'),
                ('move_goods', 'Internal Move'),
                ('input_goods', 'From Supplier'),
                ], 'Direct Document')

    type_product = fields.Selection([
                ('fixed_assets', 'Fixed Assets'),
                ('intangible', 'Intangible assets'),
                ('material', 'Material'),
                ('goods', 'Goods'),
                ], 'Type Product')

    template_invoice = fields.Function(fields.Many2One('ekd.document.template',
                'Name',
                domain=[('type_account', '=',
                    Eval('direct_document', 
                        Get(Eval('context', {}), 'direct_document')))]
                ), 'get_fields', setter='set_fields')
    number_doc = fields.Function(fields.Char('Number',
                states={'invisible': And(Not(Bool(Eval('number_doc'))),
                    Not(Equal(Eval('direct_document',''),'input_goods')))}),
                'get_fields', setter='set_fields', searcher='template_search')
    from_to_party = fields.Function(fields.Many2One('party.party',
                'Party',
                states={'invisible': Equal(Eval('direct_document'),'move_goods')}
                ), 'get_fields', setter='set_fields')
    from_party_fnc = fields.Function(fields.Many2One('party.party',
                'From Party',
                states={'invisible': Not(Equal(Eval('direct_document'),'move_goods'))}
                ), 'get_fields', setter='set_fields')
    to_party_fnc = fields.Function(fields.Many2One('party.party',
                'To Party',
                states={'invisible': Not(Equal(Eval('direct_document'),'move_goods'))}
                ), 'get_fields', setter='set_fields')
    from_to_stock = fields.Function(fields.Many2One('ekd.company.department.stock',
                'Stock',
                states={'invisible': Equal(Eval('direct_document'),'move_goods')}
                ), 'get_fields', setter='set_fields')
    from_stock_fnc = fields.Function(fields.Many2One('ekd.company.department.stock',
                'From Stock',
                states={'invisible': Not(Equal(Eval('direct_document'),'move_goods'))}
                ), 'get_fields', setter='set_fields')
    to_stock_fnc = fields.Function(fields.Many2One('ekd.company.department.stock',
                'To Stock',
                states={'invisible': Not(Equal(Eval('direct_document'),'move_goods'))}
                ), 'get_fields', setter='set_fields')
    shipper = fields.Many2One('party.party', 'Shipper',
                states={'invisible': Eval('as_shipper', True)})
    consignee = fields.Many2One('party.party', 'Consignee',
                states={'invisible': Eval('as_consignee', True)})
    as_shipper = fields.Boolean('As Shipper')
    as_consignee = fields.Boolean('As Consignee')

    amount_doc = fields.Function(fields.Numeric('Amount', digits=(16, Eval('currency_digits', 2))), 
                'get_fields', setter='set_fields')
    amount_tax = fields.Function(fields.Numeric('Amount Tax', digits=(16, Eval('currency_digits', 2))), 
                'get_fields')
    currency = fields.Many2One('currency.currency', 'Currency')
    currency_digits = fields.Function(fields.Integer('Currency Digits',
                on_change_with=['currency']), 'get_currency_digits')
    type_tax = fields.Selection([
            ('not_vat','Not VAT'),
            ('including','Including'),
            ('over_amount','Over Amount')
            ], 'Type Compute Tax')
    lines = fields.One2Many('ekd.document.line.product', 'invoice', 'Lines',
                context={'type_product':Eval('type_product')})
    state_doc = fields.Function(fields.Selection(_STATE_INVOICE_GOODS, required=True, readonly=True, string='State'), 'get_fields', 
                setter='set_fields')
    deleting = fields.Boolean('Flag deleting', readonly=True)

    def __init__(self):
        super(DocumentInvoiceGoods, self).__init__()
        self._rpc.update({
            'button_add_number': True,
            'button_draft': True,
            'button_to_pay': True,
            'button_part_paid': True,
            'button_paid': True,
            'button_received': True,
            'button_cancel': True,
            'button_restore': True,
            'button_print': True,
            'button_send': True,
            'button_post': True,
            'button_obtained': True,
            'button_delivered': True,
            })

        self._order.insert(0, ('date_document', 'ASC'))
        self._order.insert(1, ('template', 'ASC'))

    def default_state_doc(self):
        return Transaction().context.get('state') or 'draft'

    def default_as_shipper(self):
        return True

    def default_as_consignee(self):
        return True

    def default_type_product(self):
        return Transaction().context.get('type_product') or 'material'

    def default_from_to_party_doc(self):
        return Transaction().context.get('from_to_party') or False

    def default_direct_document(self):
        return Transaction().context.get('direct_document') or 'invoice_revenue'

    def default_template_invoice(self):
        context = Transaction().context
        if context.get('template_invoice', False):
            return  context.get('template_invoice')
        else:
            template_obj = self.pool.get('ekd.document.template')
            template_ids = template_obj.search( [
                        ('type_account','=',context.get('direct_document'))
                        ], order=[('sequence','ASC')])
            if len(template_ids) > 0:
                return template_ids[0]

    def default_currency(self):
        company_obj = self.pool.get('company.company')
        currency_obj = self.pool.get('currency.currency')
        context = Transaction().context
        if context.get('company'):
            company = company_obj.browse( context['company'])
            return company.currency.id
        return False

    def default_currency_digits(self):
        company_obj = self.pool.get('company.company')
        context = Transaction().context
        if context.get('company'):
            company = company_obj.browse( context['company'])
            return company.currency.digits
        return 2

    def default_state_doc(self):
        return Transaction().context.get('state_doc', 'draft')

    def get_currency_digits(self,  ids, name):
        res = {}
        for line in self.browse( ids):
            res[line.id] = line.currency and line.currency.digits or 2
        return res

    def default_amount(self):
        return Transaction().context.get('amount') or Decimal('0.0')

    def default_company(self):
        return Transaction().context.get('company') or False

    def documents_base_get(self):
        dictions_obj = self.pool.get('ir.dictions')
        res = []
        diction_ids = dictions_obj.search([
            ('model', '=', 'ekd.document.head.invoice_goods'),
            ('pole', '=', 'document_base'),
            ], order=[('sequence','ASC')])
        for diction in dictions_obj.browse(diction_ids):
            res.append([diction.key, diction.value])
        return res

    def get_fields(self,  ids, names):
        if not ids:
            return {}
        res={}
        for line in self.browse( ids):
            for name in names:
                res.setdefault(name, {})
                res[name].setdefault(line.id, False)
                if name == 'number_doc':
                    if line.direct_document == 'input_goods' :
                        res[name][line.id] = line.number_in
                    else:
                        res[name][line.id] = line.number_our
                elif name == 'state_doc':
                        res[name][line.id] = line.state
                elif name == 'amount_doc':
                    for line_spec in line.lines:
                        if line_spec.type == 'line':
                            res[name][line.id] += line_spec.amount
                elif name == 'amount_tax':
                    for line_spec in line.lines:
                        if line_spec.type == 'line':
                            res[name][line.id] += line_spec.amount_tax
                elif name == 'from_to_party':
                    if line.direct_document == 'input_goods' :
                        res[name][line.id] = line.from_party.id
                    else:
                        res[name][line.id] = line.to_party.id
                elif name == 'from_party_fnc':
                    res[name][line.id] = line.from_party.id
                elif name == 'to_party_fnc':
                    res[name][line.id] = line.to_party.id
                elif name == 'template_invoice':
                    res[name][line.id] = line.template.id
                elif name == 'note_cash':
                    res[name][line.id] = line.note
        return res

    def set_fields(self,  ids, name, value):
        if isinstance(ids, list):
            ids = ids[0]
        if not value:
            return
        document = self.browse( ids)
        if name == 'state_doc':
            self.write( ids, {'state':value, })
        elif name == 'template_invoice':
            self.write( ids, { 'template': value, })
        elif name == 'number_doc':
            if document.direct_document == 'input_goods':
                self.write( ids, { 'number_in': value, })
            else:
                self.write( ids, { 'number_our': value, })
        elif name == 'amount_doc':
            self.write( ids, { 'amount': value, })
        elif name == 'from_party_doc':
            if document.direct_document == 'input_goods':
                self.write( ids, { 'from_party': value, })
            else:
                self.write( ids, { 'to_party': value, })
        elif name == 'from_party_fnc':
            self.write( ids, { 'from_party': value, })
        elif name == 'to_party_fnc':
            self.write( ids, { 'to_party': value, })

    def template_select_get(self):
        context = Transaction().context
        template_obj = self.pool.get('ekd.document.template')
        raise Exception(str(context))
        template_ids = template_obj.search( ['type_account','=', 
                        context.get('direct_document', 'input_goods')])
        res=[]
        for template in template_obj.browse( template_ids):
            res.append([template.id,template.name])
        return res

    def template_search(self,  name, domain=[]):
        if name == 'template_bank':
            for table, exp, value in domain:
                return  [('template', 'ilike', value)]
        elif name == 'from_party_doc':
            document_obj = self.pool.get('ekd.document')
            table, exp, value = domain[0]
            find_ids = document_obj.search( [('from_party', 'ilike', value)])
            return [('document', 'in', find_ids)]
        elif name == 'to_party_doc':
            document_obj = self.pool.get('ekd.document')
            table, exp, value = domain[0]
            find_ids = document_obj.search( [('to_party', 'ilike', value)])
            return [('document', 'in', find_ids)]

    def get_rec_name(self,  ids, name):
        if not ids:
            return {}
        return self.pool.get('ekd.document').get_rec_name( ids, name)

    def on_change_document_base(self, vals):
        if not vals.get('document_base'):
            return {}
        model, model_id = vals.get('document_base').split(',')
        model_obj = self.pool.get(model)
        if model_id == '0':
            return {}
        model_line = model_obj.browse(int(model_id))
        lines_new = {}
        field_import = [
        'product_ref', 
        'type_tax', 
        'tax', 
        'type', 
        'currency', 
        'unit', 
        'currency_digits', 
        'unit_price', 
        'amount_tax', 
        'product', 
        'description', 
        'type_product', 
        'unit_digits', 
        'amount', 
        'quantity']

        if hasattr(model_obj, 'lines'):
            for line_new in model_line.lines:
                if line_new.type == 'line':
                    tmp_line = line_new.read(line_new.id, field_import)
                    tmp_line['state'] = 'draft'
                    lines_new.setdefault('add', []).append(tmp_line)
            return {
                'from_to_party': model_line.from_to_party.id,
                'parent': int(model_id),
                'lines':lines_new}
        else:
            return {
                'from_to_party': model_line.from_to_party.id,
                'parent': int(model_id)}

    def create(self, vals):
        later = {}
        vals = vals.copy()
        cursor = Transaction().cursor
        for field in vals:
            if field in self._columns\
                    and hasattr(self._columns[field], 'set'):
                later[field] = vals[field]
        for field in later:
            del vals[field]
        if cursor.nextid(self._table):
            cursor.setnextid(self._table, cursor.currid(self._table))
        new_id = super(DocumentInvoiceGoods, self).create( vals)
        invoice = self.browse( new_id)
        new_id = invoice.document.id
        cr = Transaction().cursor
        cr.execute('UPDATE "' + self._table + '" SET id = %s '\
                        'WHERE id = %s', (invoice.document.id, invoice.id))
        ModelStorage.delete(self, invoice.id)
        self.write( new_id, later)
        res = self.browse( new_id)
        return res.id

    def delete(self, ids):
        cr = Transaction().cursor
        doc_lines_obj = self.pool.get('ekd.document.line.product')
        for document in self.browse(ids):
            if document.state == 'deleted' and document.deleting:
                return super(DocumentInvoiceGoods, self).delete(ids)
            else:
                doc_lines_obj.write([x.id for x in document.lines], {'state': 'deleted', 'deleting':True})
                self.write(document.id, {'state': 'deleted', 'deleting':True})
        return True

    def button_add_number(self, ids):
        return self.new_number(ids)

    def button_post(self, ids):
        return self.post(ids)

    def button_obtained(self, ids):
        return self.obtained(ids)

    def button_delivered(self, ids):
        return self.delivered(ids)

    def button_paid(self, ids):
        return self.paid(ids)

    def button_part_paid(self, ids):
        return self.part_paid(ids)

    def button_to_pay(self, ids):
        return self.to_pay(ids)

    def button_send(self, ids):
        return self.send(ids)

    def button_print(self, ids):
        return self.print_document(ids)

    def button_cancel(self, ids):
        return self.cancel(ids)

    def button_draft(self, ids):
        return self.draft(ids)

    def button_restore(self, ids):
        return self.draft(ids)

    def new_number(self, ids):
        sequence_obj = self.pool.get('ir.sequence')
        for document in self.browse(ids):
            if document.template and document.template.sequence:
                sequence = sequence_obj.get_id(document.template.sequence.id)
            else:
                raise Exception('Error', 'Sequence for document not find!')
            self.write(document.id, {
                    'number_doc': sequence,
                    })

    def post(self, ids):
        return self.write(ids, {
            'state': 'posted',
            })

    def obtained(self, ids):
        return self.write(ids, {
            'state': 'obtained',
            })

    def delivered(self, ids):
        return self.write(ids, {
            'state': 'delivered',
            })

    def send(self, ids):
        return self.write(ids, {
            'state': 'sended',
            })

    def to_pay(self, ids):
        return self.write(ids, {
            'state': 'to_pay',
            })

    def paid(self, ids):
        return self.write(ids, {
            'state': 'paid',
            })

    def part_paid(self, ids):
        return self.write(ids, {
            'state': 'part_paid',
            })

    def print_document(self, ids):
        return self.write(ids, {
            'state': 'printed',
            })

    def draft(self, ids):
        return self.write(ids, {
                'state': 'draft',
                'deleting':False
                })

    def cancel(self, ids):
        return self.write(ids, {
                'state': 'canceled',
                })
Пример #13
0
class SaleOpportunity(Workflow, ModelSQL, ModelView, AttachmentCopyMixin,
                      NoteCopyMixin):
    'Sale Opportunity'
    __name__ = "sale.opportunity"
    _history = True
    _rec_name = 'number'

    _states_start = {
        'readonly': Eval('state') != 'lead',
    }
    _states_stop = {
        'readonly':
        Eval('state').in_(['converted', 'won', 'lost', 'cancelled']),
    }

    number = fields.Char('Number', readonly=True, required=True, select=True)
    reference = fields.Char('Reference', select=True)
    party = fields.Many2One(
        'party.party',
        "Party",
        select=True,
        states={
            'readonly': Eval('state').in_(['converted', 'lost', 'cancelled']),
            'required': ~Eval('state').in_(['lead', 'lost', 'cancelled']),
        },
        context={
            'company': Eval('company', -1),
        },
        depends={'company'})
    contact = fields.Many2One('party.contact_mechanism',
                              "Contact",
                              context={
                                  'company': Eval('company', -1),
                              },
                              search_context={
                                  'related_party': Eval('party'),
                              },
                              depends=['party', 'company'])
    address = fields.Many2One('party.address',
                              'Address',
                              domain=[('party', '=', Eval('party'))],
                              select=True,
                              states=_states_stop)
    company = fields.Many2One(
        'company.company',
        'Company',
        required=True,
        select=True,
        states={
            'readonly': _states_stop['readonly'] | Eval('party', True),
        },
        domain=[
            ('id', If(In('company', Eval('context', {})), '=',
                      '!='), Get(Eval('context', {}), 'company', 0)),
        ])
    currency = fields.Function(
        fields.Many2One('currency.currency', "Currency"),
        'on_change_with_currency')
    amount = Monetary("Amount",
                      currency='currency',
                      digits='currency',
                      states=_states_stop,
                      help='Estimated revenue amount.')
    payment_term = fields.Many2One('account.invoice.payment_term',
                                   'Payment Term',
                                   states={
                                       'readonly':
                                       In(Eval('state'),
                                          ['converted', 'lost', 'cancelled']),
                                   })
    employee = fields.Many2One(
        'company.employee',
        'Employee',
        states={
            'readonly': _states_stop['readonly'],
            'required': ~Eval('state').in_(['lead', 'lost', 'cancelled']),
        },
        domain=[('company', '=', Eval('company'))])
    start_date = fields.Date('Start Date',
                             required=True,
                             select=True,
                             states=_states_start)
    end_date = fields.Date('End Date', select=True, states=_states_stop)
    description = fields.Char('Description', states=_states_stop)
    comment = fields.Text('Comment', states=_states_stop)
    lines = fields.One2Many('sale.opportunity.line',
                            'opportunity',
                            'Lines',
                            states=_states_stop)
    conversion_probability = fields.Float(
        'Conversion Probability',
        digits=(1, 4),
        required=True,
        domain=[
            ('conversion_probability', '>=', 0),
            ('conversion_probability', '<=', 1),
        ],
        states={
            'readonly':
            ~Eval('state').in_(['opportunity', 'lead', 'converted']),
        },
        help="Percentage between 0 and 100.")
    lost_reason = fields.Text('Reason for loss',
                              states={
                                  'invisible': Eval('state') != 'lost',
                              })
    sales = fields.One2Many('sale.sale', 'origin', 'Sales')

    converted_by = employee_field(
        "Converted By", states=['converted', 'won', 'lost', 'cancelled'])
    state = fields.Selection([
        ('lead', "Lead"),
        ('opportunity', "Opportunity"),
        ('converted', "Converted"),
        ('won', "Won"),
        ('lost', "Lost"),
        ('cancelled', "Cancelled"),
    ],
                             "State",
                             required=True,
                             select=True,
                             sort=False,
                             readonly=True)

    del _states_start
    del _states_stop

    @classmethod
    def __register__(cls, module_name):
        pool = Pool()
        Sale = pool.get('sale.sale')
        transaction = Transaction()
        cursor = transaction.connection.cursor()
        update = transaction.connection.cursor()
        sql_table = cls.__table__()
        sale = Sale.__table__()

        table = cls.__table_handler__(module_name)
        number_exists = table.column_exist('number')

        # Migration from 3.8: rename reference into number
        if table.column_exist('reference') and not number_exists:
            table.column_rename('reference', 'number')
            number_exists = True

        super(SaleOpportunity, cls).__register__(module_name)
        table = cls.__table_handler__(module_name)

        # Migration from 3.4: replace sale by origin
        if table.column_exist('sale'):
            cursor.execute(*sql_table.select(
                sql_table.id, sql_table.sale, where=sql_table.sale != Null))
            for id_, sale_id in cursor:
                update.execute(
                    *sale.update(columns=[sale.origin],
                                 values=['%s,%s' % (cls.__name__, id_)],
                                 where=sale.id == sale_id))
            table.drop_column('sale')

        # Migration from 4.0: change probability into conversion probability
        if table.column_exist('probability'):
            cursor.execute(
                *sql_table.update([sql_table.conversion_probability],
                                  [sql_table.probability / 100.0]))
            table.drop_constraint('check_percentage')
            table.drop_column('probability')

        # Migration from 4.2: make employee not required
        table.not_null_action('employee', action='remove')

        # Migration from 5.0: drop required on description
        table.not_null_action('description', action='remove')

    @classmethod
    def __setup__(cls):
        super(SaleOpportunity, cls).__setup__()
        cls._order.insert(0, ('start_date', 'DESC'))
        cls._transitions |= set((
            ('lead', 'opportunity'),
            ('lead', 'lost'),
            ('lead', 'cancelled'),
            ('lead', 'converted'),
            ('opportunity', 'converted'),
            ('opportunity', 'lead'),
            ('opportunity', 'lost'),
            ('opportunity', 'cancelled'),
            ('converted', 'won'),
            ('converted', 'lost'),
            ('won', 'converted'),
            ('lost', 'converted'),
            ('lost', 'lead'),
            ('cancelled', 'lead'),
        ))
        cls._buttons.update({
            'lead': {
                'invisible':
                ~Eval('state').in_(['cancelled', 'lost', 'opportunity']),
                'icon':
                If(
                    Eval('state').in_(['cancelled', 'lost']), 'tryton-undo',
                    'tryton-back'),
                'depends': ['state'],
            },
            'opportunity': {
                'pre_validate': [
                    If(~Eval('party'), ('party', '!=', None), ()),
                    If(~Eval('employee'), ('employee', '!=', None), ()),
                ],
                'invisible':
                ~Eval('state').in_(['lead']),
                'depends': ['state'],
            },
            'convert': {
                'invisible': ~Eval('state').in_(['opportunity']),
                'depends': ['state'],
            },
            'lost': {
                'invisible': ~Eval('state').in_(['lead', 'opportunity']),
                'depends': ['state'],
            },
            'cancel': {
                'invisible': ~Eval('state').in_(['lead', 'opportunity']),
                'depends': ['state'],
            },
        })

    @staticmethod
    def default_state():
        return 'lead'

    @staticmethod
    def default_start_date():
        Date = Pool().get('ir.date')
        return Date.today()

    @staticmethod
    def default_conversion_probability():
        return 0.5

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @staticmethod
    def default_employee():
        return Transaction().context.get('employee')

    @classmethod
    def default_payment_term(cls):
        PaymentTerm = Pool().get('account.invoice.payment_term')
        payment_terms = PaymentTerm.search(cls.payment_term.domain)
        if len(payment_terms) == 1:
            return payment_terms[0].id

    @classmethod
    def view_attributes(cls):
        return super().view_attributes() + [
            ('/tree', 'visual', If(Eval('state') == 'cancelled', 'muted', '')),
        ]

    @classmethod
    def get_resources_to_copy(cls, name):
        return {
            'sale.sale',
        }

    @classmethod
    def create(cls, vlist):
        pool = Pool()
        Config = pool.get('sale.configuration')

        config = Config(1)
        vlist = [x.copy() for x in vlist]
        default_company = cls.default_company()
        for vals in vlist:
            if vals.get('number') is None:
                vals['number'] = config.get_multivalue(
                    'sale_opportunity_sequence',
                    company=vals.get('company', default_company)).get()
        return super(SaleOpportunity, cls).create(vlist)

    @classmethod
    def copy(cls, opportunities, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('number', None)
        default.setdefault('sales', None)
        default.setdefault('converted_by')
        return super(SaleOpportunity, cls).copy(opportunities, default=default)

    @fields.depends('company')
    def on_change_with_currency(self, name=None):
        if self.company:
            return self.company.currency.id

    @fields.depends('party')
    def on_change_party(self):
        if self.party and self.party.customer_payment_term:
            self.payment_term = self.party.customer_payment_term
        else:
            self.payment_term = self.default_payment_term()

    def _get_sale_opportunity(self):
        '''
        Return sale for an opportunity
        '''
        Sale = Pool().get('sale.sale')
        return Sale(
            description=self.description,
            party=self.party,
            contact=self.contact,
            payment_term=self.payment_term,
            company=self.company,
            invoice_address=self.address,
            shipment_address=self.address,
            currency=self.company.currency,
            comment=self.comment,
            sale_date=None,
            origin=self,
            warehouse=Sale.default_warehouse(),
        )

    def create_sale(self):
        '''
        Create a sale for the opportunity and return the sale
        '''
        sale = self._get_sale_opportunity()
        sale_lines = []
        for line in self.lines:
            sale_lines.append(line.get_sale_line(sale))
        sale.lines = sale_lines
        return sale

    @classmethod
    def delete(cls, opportunities):
        # Cancel before delete
        cls.cancel(opportunities)
        for opportunity in opportunities:
            if opportunity.state != 'cancelled':
                raise AccessError(
                    gettext('sale_opportunity.msg_opportunity_delete_cancel',
                            opportunity=opportunity.rec_name))
        super(SaleOpportunity, cls).delete(opportunities)

    @classmethod
    @ModelView.button
    @Workflow.transition('lead')
    def lead(cls, opportunities):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('opportunity')
    def opportunity(cls, opportunities):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('converted')
    @set_employee('converted_by')
    def convert(cls, opportunities):
        pool = Pool()
        Sale = pool.get('sale.sale')
        sales = [o.create_sale() for o in opportunities if not o.sales]
        Sale.save(sales)
        for sale in sales:
            sale.origin.copy_resources_to(sale)

    @property
    def is_forecast(self):
        pool = Pool()
        Date = pool.get('ir.date')
        with Transaction().set_context(company=self.company.id):
            today = Date.today()
        return self.end_date or datetime.date.max > today

    @classmethod
    @Workflow.transition('won')
    def won(cls, opportunities):
        pool = Pool()
        Date = pool.get('ir.date')
        for company, c_opportunities in groupby(opportunities,
                                                key=lambda o: o.company):
            with Transaction().set_context(company=company.id):
                today = Date.today()
            cls.write([o for o in c_opportunities if o.is_forecast], {
                'end_date': today,
                'state': 'won',
            })

    @classmethod
    @ModelView.button
    @Workflow.transition('lost')
    def lost(cls, opportunities):
        pool = Pool()
        Date = pool.get('ir.date')
        for company, c_opportunities in groupby(opportunities,
                                                key=lambda o: o.company):
            with Transaction().set_context(company=company.id):
                today = Date.today()
            cls.write([o for o in c_opportunities if o.is_forecast], {
                'end_date': today,
                'state': 'lost',
            })

    @classmethod
    @ModelView.button
    @Workflow.transition('cancelled')
    def cancel(cls, opportunities):
        pool = Pool()
        Date = pool.get('ir.date')
        for company, c_opportunities in groupby(opportunities,
                                                key=lambda o: o.company):
            with Transaction().set_context(company=company.id):
                today = Date.today()
            cls.write([o for o in c_opportunities if o.is_forecast], {
                'end_date': today,
                'state': 'cancelled',
            })

    @staticmethod
    def _sale_won_states():
        return ['confirmed', 'processing', 'done']

    @staticmethod
    def _sale_lost_states():
        return ['cancelled']

    def is_won(self):
        sale_won_states = self._sale_won_states()
        sale_lost_states = self._sale_lost_states()
        end_states = sale_won_states + sale_lost_states
        return (self.sales and all(s.state in end_states for s in self.sales)
                and any(s.state in sale_won_states for s in self.sales))

    def is_lost(self):
        sale_lost_states = self._sale_lost_states()
        return (self.sales
                and all(s.state in sale_lost_states for s in self.sales))

    @property
    def sale_amount(self):
        pool = Pool()
        Currency = pool.get('currency.currency')

        if not self.sales:
            return

        sale_lost_states = self._sale_lost_states()
        amount = 0
        for sale in self.sales:
            if sale.state not in sale_lost_states:
                amount += Currency.compute(sale.currency, sale.untaxed_amount,
                                           self.currency)
        return amount

    @classmethod
    def process(cls, opportunities):
        won = []
        lost = []
        converted = []
        for opportunity in opportunities:
            sale_amount = opportunity.sale_amount
            if opportunity.amount != sale_amount:
                opportunity.amount = sale_amount
            if opportunity.is_won():
                won.append(opportunity)
            elif opportunity.is_lost():
                lost.append(opportunity)
            elif (opportunity.state != 'converted' and opportunity.sales):
                converted.append(opportunity)
        cls.save(opportunities)
        if won:
            cls.won(won)
        if lost:
            cls.lost(lost)
        if converted:
            cls.convert(converted)
Пример #14
0
class Journal(ModelSQL, ModelView):
    'Journal'
    _name = 'ekd.account.journal'
    _description = __doc__

    name = fields.Char('Name', size=None, required=True, translate=True)
    code = fields.Char('Code', size=None)
    active = fields.Boolean('Active', select=2)
    type = fields.Selection('get_types', 'Type', required=True)
    view = fields.Many2One('ekd.account.journal.view', 'View')
    centralised = fields.Boolean('Centralised counterpart')
    update_posted = fields.Boolean('Allow cancelling moves')
    sequence = fields.Property(fields.Many2One('ir.sequence', 'Sequence',
        domain=[('code', '=', 'ekd.account.journal')],
        context={'code': 'ekd.account.journal'},
        states={
            'required': Bool(Get(Eval('context', {}), 'company', 0)),
        }))
    credit_account = fields.Property(fields.Many2One('ekd.account',
        'Default Credit Account', domain=[
            ('kind', '!=', 'view'),
            ('company', '=', Get(Eval('context', {}), 'company', 0)),
        ],
        states={
            'required': And(Or(Bool(Eval('centralised')),
                Equal(Eval('type'), 'cash')),
                Bool(Get(Eval('context', {}), 'company', 0))),
            'invisible': Not(Bool(Get(Eval('context', {}), 'company', 0))),
        }, depends=['type', 'centralised']))
    debit_account = fields.Property(fields.Many2One('ekd.account',
        'Default Debit Account', domain=[
            ('kind', '!=', 'view'),
            ('company', '=', Get(Eval('context', {}), 'company', 0)),
        ],
        states={
            'required': And(Or(Bool(Eval('centralised')),
                Equal(Eval('type'), 'cash')),
                Bool(Get(Eval('context', {}), 'company', 0))),
            'invisible': Not(Bool(Get(Eval('context', {}), 'company', 0))),
        }, depends=['type', 'centralised']))

    def __init__(self):
        super(Journal, self).__init__()
        self._order.insert(0, ('name', 'ASC'))

    #def init(self, module_name):
    #    super(Journal, self).init(module_name)
    #    cursor = Transaction().cursor
    #    table = TableHandler(cursor, self, module_name)

    def default_active(self):
        return True

    def default_centralised(self):
        return False

    def default_update_posted(self):
        return False

    def default_sequence(self):
        return False

    def get_types(self):
        type_obj = self.pool.get('ekd.account.journal.type')
        type_ids = type_obj.search([])
        types = type_obj.browse(type_ids)
        return [(x.code, x.name) for x in types]

    def search_rec_name(self, name, clause):
        ids = self.search([
            ('code',) + clause[1:],
            ], limit=1, order=[])
        if ids:
            return [('code',) + clause[1:]]
        return [(self._rec_name,) + clause[1:]]
Пример #15
0
class SaleOpportunity(Workflow, ModelSQL, ModelView):
    'Sale Opportunity'
    __name__ = "sale.opportunity"
    _history = True
    _rec_name = 'number'
    number = fields.Char('Number', readonly=True, required=True, select=True)
    reference = fields.Char('Reference', select=True)
    party = fields.Many2One(
        'party.party',
        'Party',
        select=True,
        states={
            'readonly': Eval('state').in_(['converted', 'lost', 'cancelled']),
            'required': ~Eval('state').in_(['lead', 'lost', 'cancelled']),
        },
        depends=['state'])
    address = fields.Many2One('party.address',
                              'Address',
                              domain=[('party', '=', Eval('party'))],
                              select=True,
                              depends=['party', 'state'],
                              states=_STATES_STOP)
    company = fields.Many2One('company.company',
                              'Company',
                              required=True,
                              select=True,
                              states=_STATES_STOP,
                              domain=[
                                  ('id',
                                   If(In('company', Eval('context', {})), '=',
                                      '!='),
                                   Get(Eval('context', {}), 'company', 0)),
                              ],
                              depends=_DEPENDS_STOP)
    currency = fields.Function(
        fields.Many2One('currency.currency', 'Currency'), 'get_currency')
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'get_currency_digits')
    amount = fields.Numeric('Amount',
                            digits=(16, Eval('currency_digits', 2)),
                            states=_STATES_STOP,
                            depends=_DEPENDS_STOP + ['currency_digits'],
                            help='Estimated revenue amount')
    payment_term = fields.Many2One('account.invoice.payment_term',
                                   'Payment Term',
                                   states={
                                       'readonly':
                                       In(Eval('state'),
                                          ['converted', 'lost', 'cancelled']),
                                   },
                                   depends=['state'])
    employee = fields.Many2One(
        'company.employee',
        'Employee',
        states={
            'readonly': _STATES_STOP['readonly'],
            'required': ~Eval('state').in_(['lead', 'lost', 'cancelled']),
        },
        depends=['state', 'company'],
        domain=[('company', '=', Eval('company'))])
    start_date = fields.Date('Start Date',
                             required=True,
                             select=True,
                             states=_STATES_START,
                             depends=_DEPENDS_START)
    end_date = fields.Date('End Date',
                           select=True,
                           states=_STATES_STOP,
                           depends=_DEPENDS_STOP)
    description = fields.Char('Description',
                              required=True,
                              states=_STATES_STOP,
                              depends=_DEPENDS_STOP)
    comment = fields.Text('Comment',
                          states=_STATES_STOP,
                          depends=_DEPENDS_STOP)
    lines = fields.One2Many('sale.opportunity.line',
                            'opportunity',
                            'Lines',
                            states=_STATES_STOP,
                            depends=_DEPENDS_STOP)
    state = fields.Selection(STATES,
                             'State',
                             required=True,
                             select=True,
                             sort=False,
                             readonly=True)
    conversion_probability = fields.Float(
        'Conversion Probability',
        digits=(1, 4),
        required=True,
        domain=[
            ('conversion_probability', '>=', 0),
            ('conversion_probability', '<=', 1),
        ],
        states={
            'readonly':
            ~Eval('state').in_(['opportunity', 'lead', 'converted']),
        },
        depends=['state'],
        help="Percentage between 0 and 100")
    lost_reason = fields.Text('Reason for loss',
                              states={
                                  'invisible': Eval('state') != 'lost',
                              },
                              depends=['state'])
    sales = fields.One2Many('sale.sale', 'origin', 'Sales')

    @classmethod
    def __register__(cls, module_name):
        pool = Pool()
        Sale = pool.get('sale.sale')
        cursor = Transaction().connection.cursor()
        sql_table = cls.__table__()
        sale = Sale.__table__()

        table = cls.__table_handler__(module_name)
        number_exists = table.column_exist('number')

        # Migration from 3.8: rename reference into number
        if table.column_exist('reference') and not number_exists:
            table.column_rename('reference', 'number')
            number_exists = True

        super(SaleOpportunity, cls).__register__(module_name)
        table = cls.__table_handler__(module_name)

        # Migration from 3.4: replace sale by origin
        if table.column_exist('sale'):
            cursor.execute(*sql_table.select(
                sql_table.id, sql_table.sale, where=sql_table.sale != Null))
            for id_, sale_id in cursor.fetchall():
                cursor.execute(
                    *sale.update(columns=[sale.origin],
                                 values=['%s,%s' % (cls.__name__, id_)],
                                 where=sale.id == sale_id))
            table.drop_column('sale')

        # Migration from 4.0: change probability into conversion probability
        if table.column_exist('probability'):
            cursor.execute(
                *sql_table.update([sql_table.conversion_probability],
                                  [sql_table.probability / 100.0]))
            table.drop_constraint('check_percentage')
            table.drop_column('probability')

        # Migration from 4.2: make employee not required
        table.not_null_action('employee', action='remove')

    @classmethod
    def __setup__(cls):
        super(SaleOpportunity, cls).__setup__()
        cls._order.insert(0, ('start_date', 'DESC'))
        cls._error_messages.update({
            'delete_cancel': ('Sale Opportunity "%s" must be cancelled '
                              'before deletion.'),
        })
        cls._transitions |= set((
            ('lead', 'opportunity'),
            ('lead', 'lost'),
            ('lead', 'cancelled'),
            ('lead', 'converted'),
            ('opportunity', 'converted'),
            ('opportunity', 'lead'),
            ('opportunity', 'lost'),
            ('opportunity', 'cancelled'),
            ('converted', 'won'),
            ('converted', 'lost'),
            ('won', 'converted'),
            ('lost', 'converted'),
            ('lost', 'lead'),
            ('cancelled', 'lead'),
        ))
        cls._buttons.update({
            'lead': {
                'invisible':
                ~Eval('state').in_(['cancelled', 'lost', 'opportunity']),
                'icon':
                If(
                    Eval('state').in_(['cancelled', 'lost']), 'tryton-undo',
                    'tryton-back'),
                'depends': ['state'],
            },
            'opportunity': {
                'invisible': ~Eval('state').in_(['lead']),
                'depends': ['state'],
            },
            'convert': {
                'invisible': ~Eval('state').in_(['opportunity']),
                'depends': ['state'],
            },
            'lost': {
                'invisible': ~Eval('state').in_(['lead', 'opportunity']),
                'depends': ['state'],
            },
            'cancel': {
                'invisible': ~Eval('state').in_(['lead', 'opportunity']),
                'depends': ['state'],
            },
        })

    @staticmethod
    def default_state():
        return 'lead'

    @staticmethod
    def default_start_date():
        Date = Pool().get('ir.date')
        return Date.today()

    @staticmethod
    def default_conversion_probability():
        return 0.5

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @staticmethod
    def default_employee():
        return Transaction().context.get('employee')

    @classmethod
    def default_payment_term(cls):
        PaymentTerm = Pool().get('account.invoice.payment_term')
        payment_terms = PaymentTerm.search(cls.payment_term.domain)
        if len(payment_terms) == 1:
            return payment_terms[0].id

    @classmethod
    def create(cls, vlist):
        pool = Pool()
        Sequence = pool.get('ir.sequence')
        Config = pool.get('sale.configuration')

        config = Config(1)
        vlist = [x.copy() for x in vlist]
        for vals in vlist:
            if vals.get('number') is None:
                vals['number'] = Sequence.get_id(
                    config.sale_opportunity_sequence.id)
        return super(SaleOpportunity, cls).create(vlist)

    @classmethod
    def copy(cls, opportunities, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('number', None)
        default.setdefault('sales', None)
        return super(SaleOpportunity, cls).copy(opportunities, default=default)

    def get_currency(self, name):
        return self.company.currency.id

    def get_currency_digits(self, name):
        return self.company.currency.digits

    @fields.depends('company')
    def on_change_company(self):
        if self.company:
            self.currency = self.company.currency
            self.currency_digits = self.company.currency.digits

    @fields.depends('party')
    def on_change_party(self):
        if self.party and self.party.customer_payment_term:
            self.payment_term = self.party.customer_payment_term
        else:
            self.payment_term = self.default_payment_term()

    def _get_sale_opportunity(self):
        '''
        Return sale for an opportunity
        '''
        Sale = Pool().get('sale.sale')
        return Sale(
            description=self.description,
            party=self.party,
            payment_term=self.payment_term,
            company=self.company,
            invoice_address=self.address,
            shipment_address=self.address,
            currency=self.company.currency,
            comment=self.comment,
            sale_date=None,
            origin=self,
            warehouse=Sale.default_warehouse(),
        )

    def create_sale(self):
        '''
        Create a sale for the opportunity and return the sale
        '''
        sale = self._get_sale_opportunity()
        sale_lines = []
        for line in self.lines:
            sale_lines.append(line.get_sale_line(sale))
        sale.lines = sale_lines
        return sale

    @classmethod
    def delete(cls, opportunities):
        # Cancel before delete
        cls.cancel(opportunities)
        for opportunity in opportunities:
            if opportunity.state != 'cancelled':
                cls.raise_user_error('delete_cancel', opportunity.rec_name)
        super(SaleOpportunity, cls).delete(opportunities)

    @classmethod
    @ModelView.button
    @Workflow.transition('lead')
    def lead(cls, opportunities):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('opportunity')
    def opportunity(cls, opportunities):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('converted')
    def convert(cls, opportunities):
        pool = Pool()
        Sale = pool.get('sale.sale')
        sales = [o.create_sale() for o in opportunities if not o.sales]
        Sale.save(sales)

    @property
    def is_forecast(self):
        pool = Pool()
        Date = pool.get('ir.date')
        today = Date.today()
        return self.end_date or datetime.date.max > today

    @classmethod
    @Workflow.transition('won')
    def won(cls, opportunities):
        pool = Pool()
        Date = pool.get('ir.date')
        cls.write([o for o in opportunities if o.is_forecast], {
            'end_date': Date.today(),
            'state': 'won',
        })

    @classmethod
    @ModelView.button
    @Workflow.transition('lost')
    def lost(cls, opportunities):
        Date = Pool().get('ir.date')
        cls.write([o for o in opportunities if o.is_forecast], {
            'end_date': Date.today(),
            'state': 'lost',
        })

    @classmethod
    @ModelView.button
    @Workflow.transition('cancelled')
    def cancel(cls, opportunities):
        Date = Pool().get('ir.date')
        cls.write([o for o in opportunities if o.is_forecast], {
            'end_date': Date.today(),
        })

    @staticmethod
    def _sale_won_states():
        return ['confirmed', 'processing', 'done']

    @staticmethod
    def _sale_lost_states():
        return ['cancel']

    def is_won(self):
        sale_won_states = self._sale_won_states()
        sale_lost_states = self._sale_lost_states()
        end_states = sale_won_states + sale_lost_states
        return (self.sales and all(s.state in end_states for s in self.sales)
                and any(s.state in sale_won_states for s in self.sales))

    def is_lost(self):
        sale_lost_states = self._sale_lost_states()
        return (self.sales
                and all(s.state in sale_lost_states for s in self.sales))

    @property
    def sale_amount(self):
        pool = Pool()
        Currency = pool.get('currency.currency')

        if not self.sales:
            return

        sale_lost_states = self._sale_lost_states()
        amount = 0
        for sale in self.sales:
            if sale.state not in sale_lost_states:
                amount += Currency.compute(sale.currency, sale.untaxed_amount,
                                           self.currency)
        return amount

    @classmethod
    def process(cls, opportunities):
        won = []
        lost = []
        converted = []
        for opportunity in opportunities:
            sale_amount = opportunity.sale_amount
            if opportunity.amount != sale_amount:
                opportunity.amount = sale_amount
            if opportunity.is_won():
                won.append(opportunity)
            elif opportunity.is_lost():
                lost.append(opportunity)
            elif (opportunity.state != 'converted' and opportunity.sales):
                converted.append(opportunity)
        cls.save(opportunities)
        if won:
            cls.won(won)
        if lost:
            cls.lost(lost)
        if converted:
            cls.convert(converted)
Пример #16
0
class Contract(ModelWorkflow, ModelSQL, ModelView):
    """Contract Agreement"""
    _name = 'contract.contract'
    _description = __doc__

    company = fields.Many2One('company.company', 'Company', required=True,
                         states=STATES, select=1, domain=[
                             ('id', If(In('company', Eval('context', {})), '=', '!='),
                              Get(Eval('context', {}), 'company', 0)),
                         ])
    journal = fields.Many2One('account.journal', 'Journal', required=True, 
                              states=STATES, domain=[('centralised', '=', False)])
    name = fields.Char('Name', required=True, translate=True)
    description = fields.Char('Description', size=None, translate=True)
    reference = fields.Char('Reference', size=None, translate=True,
                            states=STATES, help='Use for purchase orders')
    party = fields.Many2One('party.party', 'Party', required=True, 
                            states=STATES, on_change=['party'])
    product = fields.Many2One('product.product', 'Product', required=True,
                              states=STATES)
    list_price = fields.Numeric('List Price', states=STATES,
                                digits=(16, 4),
                               help='''Fixed-price override. Leave at 0.0000 for no override. Use discount at 100% to set actual price to 0.0''')
    discount = fields.Numeric('Discount (%)', states=STATES,
                              digits=(4,2),
                             help='Discount percentage on the list_price')
    quantity = fields.Numeric('Quantity', digits=(16,2), states=STATES)
    payment_term = fields.Many2One('account.invoice.payment_term',
                                   'Payment Term', required=True,
                                   states=STATES)
    state = fields.Selection([
        ('draft','Draft'),
        ('active','Active'),
        ('hold', 'Hold'),
        ('canceled','Canceled'),
    ], 'State', readonly=True)
    interval = fields.Selection([
        ('day','Day'),
        ('week','Week'),
        ('month','Month'),
        ('year','Year'),
    ], 'Interval', required=True, states=STATES)
    interval_quant = fields.Integer('Interval count', states=STATES)
    next_invoice_date = fields.Date('Next Invoice', states=STATES)
    opt_invoice_date = fields.Date('Temporary Next Invoice', readonly=True)
    start_date = fields.Date('Since', states=STATES)
    stop_date = fields.Date('Until')
    lines = fields.One2Many('account.invoice.line', 'contract', 'Invoice Lines',
                           readonly=True, domain=[('contract','=',Eval('id'))])


    def __init__(self):
        super(Contract, self).__init__()
        self._rpc.update({
            'create_next_invoice': True,
            'create_invoice_batch': True,
            'cancel_with_credit': True,
        })

    def default_state(self):
        return 'draft'

    def default_interval(self):
        return 'month'

    def default_quantity(self):
        return Decimal('1.0')

    def default_company(self):
        return Transaction().context.get('company') or False

    def default_start_date(self):
        return datetime.date.fromtimestamp(time.time())

    def default_interval_quant(self):
        return Decimal("1.0")

    def default_payment_term(self):
        config_obj = self.pool.get('contract.configuration')
        config = config_obj.browse(1)
        if config.payment_term:
            return config.payment_term.id

        company_id = self.default_company()
        company_obj = self.pool.get('company.company')
        company = company_obj.browse(company_id)
        if company and company.payment_term:
            return company.payment_term.id
        return False

    def default_journal(self):
        journal_obj = self.pool.get('account.journal')
        journal_ids = journal_obj.search([('type','=','revenue')], limit=1)
        if journal_ids:
            return journal_ids[0]
        return False

    def on_change_party(self, vals):
        party_obj = self.pool.get('party.party')
        address_obj = self.pool.get('party.address')
        payment_term_obj = self.pool.get('account.invoice.payment_term')
        res = {
            'invoice_address': False,
        }
        if vals.get('party'):
            party = party_obj.browse(vals['party'])
            payment_term = party.payment_term or False
            discount = party.discount or Decimal("0.0")
            if payment_term:
                res['payment_term'] = payment_term.id
                res['payment_term.rec_name'] = payment_term_obj.browse(
                    res['payment_term']).rec_name
            res['discount'] = discount
        return res

    def write(self, ids, vals):
        if 'state' in vals:
            self.workflow_trigger_trigger(ids)

        return super(Contract, self).write(ids, vals)

    def _invoice_init(self, contract, invoice_date):
        invoice_obj = self.pool.get('account.invoice')
        invoice_address = contract.party.address_get(contract.party.id, type='invoice')
        config_obj = self.pool.get('contract.configuration')
        config = config_obj.browse(1)
        description = config.description

        invoice = invoice_obj.create(dict(
            company=contract.company.id,
            type='out_invoice',
            description=description,
            state='draft',
            currency=contract.company.currency.id,
            journal=contract.journal.id,
            account=contract.party.account_receivable.id or contract.company.account_receivable.id,
            payment_term=contract.party.payment_term.id or contract.payment_term.id,
            party=contract.party.id,
            invoice_address=invoice_address,
            invoice_date=invoice_date,
        ))
        return invoice_obj.browse([invoice])[0]

    def _contract_unit_price(self, contract):
        unit_price = contract.product.list_price
        if contract.list_price:
            unit_price = contract.list_price
        elif contract.discount:
            unit_price = contract.product.list_price - (contract.product.list_price * contract.discount / 100)
        return unit_price


    def _invoice_append(self, invoice, contract, period):
        (last_date, next_date, quant) = period
        line_obj = self.pool.get('account.invoice.line')

        quantity = Decimal("%f" % quant)
        unit_price = self._contract_unit_price(contract)
        if not unit_price:
            # skip this contract
            log.info("skip contract without unit_price: %s contract.product.list_price: %s contract.list_price: %s contract.discount: %s" %(
                unit_price, contract.product.list_price, contract.list_price,
                contract.discount))
            return

        linedata = dict(
            type='line',
            product=contract.product.id,
            invoice=invoice.id,
            description="%s: %s (%s - %s)" % (contract.name, contract.product.name, last_date, next_date),
            quantity=quantity,
            unit=contract.product.default_uom.id,
            unit_price=unit_price,
            contract=contract.id,
            taxes=[],
        )

        account = contract.product.get_account([contract.product.id],'account_revenue_used')
        if account: 
            linedata['account'] = account.popitem()[1]

        tax_rule_obj = self.pool.get('account.tax.rule')
        taxes = contract.product.get_taxes([contract.product.id], 'customer_taxes_used')
        for tax in taxes[contract.product.id]:
            pattern = {}
            if contract.party.customer_tax_rule:
                tax_ids = tax_rule_obj.apply(contract.party.customer_tax_rule, tax, pattern)
                if tax_ids:
                    for tax_id in tax_ids:
                        linedata['taxes'].append(('add',tax_id))
                    continue
            linedata['taxes'].append(('add',tax))

        if contract.reference and not invoice.reference:
            invoice_obj = self.pool.get('account.invoice')
            invoice_obj.write(invoice.id, {'reference': contract.reference})

        return line_obj.create(linedata)

    def cancel_with_credit(self, ids):
        """ 
        
        Contract is canceled. Open invoices are credited.
        i.e. in case of failure to provide proper and
        valid credentials such as an initial payment.

        """
        contract_obj = self.pool.get('contract.contract')
        contract = self.browse(ids)[0]
        if not contract.state == 'active':
            return {}

        for id in ids:
            self.workflow_trigger_validate(id, 'cancel')

        invoice_obj = self.pool.get('account.invoice')
        invoice_line_obj = self.pool.get('account.invoice.line')
        line_ids = invoice_line_obj.search([('contract','in',ids)])
        if not line_ids:
            return {}

        invoices = []
        lines = invoice_line_obj.browse(line_ids)
        for l in lines:
            invoices.append(l.invoice.id)
        invoice_obj.credit(invoices, refund=True)

        return {}

    def create_next_invoice(self, ids, data=None):
        if data.get('form') and data['form'].get('invoice_date'):
            invoice_date = data['form']['invoice_date']
        else:
            invoice_date = datetime.date.today()

        contract_obj = self.pool.get('contract.contract')
        contract = self.browse(ids)[0]

        period = self._check_contract(contract, invoice_date)
        if not period: return {}

        log.debug("invoice_date: %s period: %s" %(invoice_date, period))

        ## create a new invoice
        invoice = self._invoice_init(contract, invoice_date)

        ## create invoice lines
        line = self._invoice_append(invoice, contract, period)
        self.write(contract.id, {'opt_invoice_date': period[1]})


        return invoice.id


    def _check_contract(self, contract, invoice_date):
        """
        returns tuple 'period' or False 

        False is returned if this contract is not up for billing at this 
        moment.

        period is defined as (last_date, next_date, quantity) 
        where the first two are datetime values indicating the billing period this
        contract is valid for, and the last indicates the quantity on the
        invoice line (months, weeks, years)

        """

        if not contract.state == 'active':
            return {}

        # don't invoice contracts unless they are due within 30 days
        # after the invoice_date
        end = invoice_date + datetime.timedelta(NOTICE_DAYS)
        
        if contract.next_invoice_date and end < contract.next_invoice_date:
            log.info('too early to invoice: %s + %d days < %s' %(invoice_date,
                                                                 NOTICE_DAYS,
                                                                 end))
            return False

        last_date = contract.next_invoice_date or contract.start_date or invoice_date
        next_date = last_date
        quant = 0

        while next_date < end:
            if contract.interval == 'year':
                next_date = next_date + relativedelta(years=contract.interval_quant)
                quant = relativedelta(next_date, last_date).years

            elif contract.interval == 'month':
                next_date = next_date + relativedelta(months=contract.interval_quant)
                delta = relativedelta(next_date, last_date)
                quant = delta.years * 12 + delta.months

            elif contract.interval == 'week':
                next_date = next_date + relativedelta(weeks=contract.interval_quant)
                quant = (next_date - last_date).days/7

            elif contract.interval == 'day':
                next_date = next_date + relativedelta(days=contract.interval_quant)
                quant = (next_date - last_date).days

        if next_date and contract.stop_date and next_date > contract.stop_date:
            log.info('contract stopped: %s > %s' % (next_date, contract.stop_date))
            return False

        quant = quant * contract.quantity

        log.debug("last_date: %s next_date: %s quant: %d" % (last_date,next_date,quant))
        return (last_date, next_date, quant)

    def create_invoice_batch(self, party=None, data=None):
        if data and data.get('form') and data['form'].get('invoice_date'):
            invoice_date = data['form']['invoice_date']
        else:
            invoice_date = datetime.date.today()

        log.info("create invoice batch with invoice_date: %s" % invoice_date)

        contract_obj = self.pool.get('contract.contract')

        contract_ids = None
        if data and data.get('model') == 'contract.contract':
            contract_ids = data.get('ids')
        if not contract_ids:
            """ 
            get a list of all active contracts
            """
            query = [('state','=','active'), 
                     ('start_date','<=',invoice_date),
                    ]

            """
            filter on party if required
            """
            if party:
                if type(party) != type([1,]):
                    party = [party,]
                query.append(('party','in',party))

            contract_ids = contract_obj.search(query)

            if not contract_ids:
                return []

        """
        build the list of all billable contracts
        and aggragate the result per party
        """
        batch = {}
        contracts = contract_obj.browse(contract_ids)
        for contract in contracts:
            period = self._check_contract(contract, invoice_date)
            if period and period[2]:
                key = contract.party.id
                if not batch.get(key): batch[key] = []
                batch[key].append((contract, period))

        """
        create draft invoices per party with lines
        for all billable contracts
        """
        res = []
        for party, info in batch.items():
            invoice = self._invoice_init(info[0][0], invoice_date)
            for (contract, period) in info:
                self._invoice_append(invoice, contract, period)
                self.write(contract.id, {'opt_invoice_date': period[1]})
            res.append(invoice.id)
        return res
Пример #17
0
class PurchaseRequest(ModelSQL, ModelView):
    'Purchase Request'
    _name = 'purchase.request'
    _description = __doc__

    product = fields.Many2One('product.product',
                              'Product',
                              required=True,
                              select=1,
                              readonly=True,
                              domain=[('purchasable', '=', True)])
    party = fields.Many2One('party.party', 'Party', select=1)
    quantity = fields.Float('Quantity', required=True)
    uom = fields.Many2One('product.uom', 'UOM', required=True, select=1)
    computed_quantity = fields.Float('Computed Quantity', readonly=True)
    computed_uom = fields.Many2One('product.uom',
                                   'Computed UOM',
                                   readonly=True)
    purchase_date = fields.Date('Best Purchase Date', readonly=True)
    supply_date = fields.Date('Expected Supply Date', readonly=True)
    stock_level = fields.Float('Stock at Supply Date', readonly=True)
    warehouse = fields.Many2One('stock.location',
                                "Warehouse",
                                required=True,
                                domain=[('type', '=', 'warehouse')],
                                readonly=True)
    purchase_line = fields.Many2One('purchase.line',
                                    'Purchase Line',
                                    readonly=True)
    purchase = fields.Function(
        fields.Many2One('purchase.purchase', 'Purchase'), 'get_purchase')
    company = fields.Many2One('company.company',
                              'Company',
                              required=True,
                              readonly=True,
                              domain=[
                                  ('id',
                                   If(In('company', Eval('context', {})), '=',
                                      '!='),
                                   Get(Eval('context', {}), 'company', 0)),
                              ])
    origin = fields.Reference('Origin',
                              selection='origin_get',
                              readonly=True,
                              required=True)
    state = fields.Function(
        fields.Selection([
            ('purchased', 'Purchased'),
            ('done', 'Done'),
            ('draft', 'Draft'),
            ('cancel', 'Cancel'),
        ], 'State'), 'get_state')

    def __init__(self):
        super(PurchaseRequest, self).__init__()
        self._order[0] = ('id', 'DESC')
        self._error_messages.update({
            'create_request':
            'Purchase requests are only created by the system.',
        })

    def get_rec_name(self, ids, name):
        if isinstance(ids, (int, long)):
            ids = [ids]
        res = {}
        for pr in self.browse(ids):
            res[pr.id] = "%s@%s" % (pr.product.name, pr.warehouse.name)
        return res

    def search_rec_name(self, name, clause):
        res = []
        names = clause[2].split('@', 1)
        res.append(('product.template.name', clause[1], names[0]))
        if len(names) != 1 and names[1]:
            res.append(('warehouse', clause[1], names[1]))
        return res

    def default_company(self):
        return Transaction().context.get('company') or False

    def get_purchase(self, ids, name):
        res = {}

        requests = self.browse(ids)
        for request in requests:
            if request.purchase_line:
                res[request.id] = request.purchase_line.purchase.id
            else:
                res[request.id] = False
        return res

    def get_state(self, ids, name):
        res = {}.fromkeys(ids, 'draft')
        for request in self.browse(ids):
            if request.purchase_line:
                if request.purchase_line.purchase.state == 'cancel':
                    res[request.id] = 'cancel'
                elif request.purchase_line.purchase.state == 'done':
                    res[request.id] = 'done'
                else:
                    res[request.id] = 'purchased'
        return res

    def origin_get(self):
        model_obj = self.pool.get('ir.model')
        res = []
        model_ids = model_obj.search([
            ('model', '=', 'stock.order_point'),
        ])
        for model in model_obj.browse(model_ids):
            res.append([model.model, model.name])
        return res

    def generate_requests(self):
        """
        For each product compute the purchase request that must be
        create today to meet product outputs.
        """
        order_point_obj = self.pool.get('stock.order_point')
        purchase_request_obj = self.pool.get('purchase.request')
        product_obj = self.pool.get('product.product')
        location_obj = self.pool.get('stock.location')
        user_obj = self.pool.get('res.user')
        company = user_obj.browse(Transaction().user).company

        # fetch warehouses:
        warehouse_ids = location_obj.search([
            ('type', '=', 'warehouse'),
        ])
        # fetch order points
        order_point_ids = order_point_obj.search([
            ('type', '=', 'purchase'),
        ])
        # index them by product
        product2ops = {}
        for order_point in order_point_obj.browse(order_point_ids):
            product2ops[(order_point.warehouse_location.id,
                         order_point.product.id)] = order_point

        # fetch stockable products
        product_ids = product_obj.search([
            ('type', '=', 'stockable'),
            ('purchasable', '=', True),
        ])
        #aggregate product by minimum supply date
        date2products = {}
        for product in product_obj.browse(product_ids):
            min_date, max_date = self.get_supply_dates(product)
            date2products.setdefault(min_date, []).append((product, max_date))

        # compute requests
        new_requests = []
        for min_date in date2products:
            product_ids = [x[0].id for x in date2products[min_date]]
            with Transaction().set_context(forecast=True,
                                           stock_date_end=min_date
                                           or datetime.date.max,
                                           stock_skip_warehouse=True):
                pbl = product_obj.products_by_location(warehouse_ids,
                                                       product_ids,
                                                       with_childs=True,
                                                       skip_zero=False)
            for product, max_date in date2products[min_date]:
                for warehouse_id in warehouse_ids:
                    qty = pbl.pop((warehouse_id, product.id))
                    order_point = product2ops.get((warehouse_id, product.id))
                    # Search for shortage between min-max
                    shortage_date, product_quantity = self.get_shortage(
                        warehouse_id,
                        product.id,
                        min_date,
                        max_date,
                        min_date_qty=qty,
                        order_point=order_point)

                    if shortage_date == None or product_quantity == None:
                        continue
                    # generate request values
                    request_val = self.compute_request(product, warehouse_id,
                                                       shortage_date,
                                                       product_quantity,
                                                       company, order_point)
                    new_requests.append(request_val)

        new_requests = self.compare_requests(new_requests)

        self.create_requests(new_requests)
        return {}

    def create_requests(self, new_requests):
        request_obj = self.pool.get('purchase.request')

        for new_req in new_requests:
            if new_req['supply_date'] == datetime.date.max:
                new_req['supply_date'] = None
            if new_req['quantity'] > 0.0:
                new_req.update({
                    'product': new_req['product'].id,
                    'party': new_req['party'] and new_req['party'].id,
                    'uom': new_req['uom'].id,
                    'computed_uom': new_req['computed_uom'].id,
                    'company': new_req['company'].id
                })
                request_obj.create(new_req)

    def compare_requests(self, new_requests):
        """
        Compare new_requests with already existing request to avoid
        to re-create existing requests.
        """
        # delete purchase request without a purchase line
        uom_obj = self.pool.get('product.uom')
        request_obj = self.pool.get('purchase.request')
        product_supplier_obj = self.pool.get('purchase.product_supplier')
        req_ids = request_obj.search([
            ('purchase_line', '=', False),
            ('origin', 'like', 'stock.order_point,%'),
        ])
        request_obj.delete(req_ids)

        req_ids = request_obj.search([
            ('purchase_line.moves', '=', False),
            ('purchase_line.purchase.state', '!=', 'cancel'),
            ('origin', 'like', 'stock.order_point,%'),
        ])
        requests = request_obj.browse(req_ids)
        # Fetch data from existing requests
        existing_req = {}
        for request in requests:
            pline = request.purchase_line
            # Skip incoherent request
            if request.product.id != pline.product.id or \
                    request.warehouse.id != pline.purchase.warehouse.id:
                continue
            # Take smallest amount between request and purchase line
            req_qty = uom_obj.compute_qty(request.uom, request.quantity,
                                          pline.unit)
            if req_qty < pline.quantity:
                quantity = request.quantity
                uom = request.uom
            else:
                quantity = pline.quantity
                uom = pline.unit

            existing_req.setdefault(
                (request.product.id, request.warehouse.id), []).append({
                    'supply_date':
                    request.supply_date or datetime.date.max,
                    'quantity':
                    quantity,
                    'uom':
                    uom
                })

        for i in existing_req.itervalues():
            i.sort(lambda r, s: cmp(r['supply_date'], s['supply_date']))

        # Update new requests to take existing requests into account
        new_requests.sort(lambda r, s: cmp(r['supply_date'], s['supply_date']))
        for new_req in new_requests:
            for old_req in existing_req.get(
                (new_req['product'].id, new_req['warehouse']), []):
                if old_req['supply_date'] <= new_req['supply_date']:
                    quantity = uom_obj.compute_qty(old_req['uom'],
                                                   old_req['quantity'],
                                                   new_req['uom'])
                    new_req['quantity'] = max(0.0,
                                              new_req['quantity'] - quantity)
                    new_req['computed_quantity'] = new_req['quantity']
                    old_req['quantity'] = uom_obj.compute_qty(
                        new_req['uom'], max(0.0,
                                            quantity - new_req['quantity']),
                        old_req['uom'])
                else:
                    break

        return new_requests

    def get_supply_dates(self, product):
        """
        Return the minimal interval of earliest supply dates for a product.

        :param product: a BrowseRecord of the Product
        :return: a tuple with the two dates
        """
        product_supplier_obj = self.pool.get('purchase.product_supplier')
        date_obj = self.pool.get('ir.date')

        min_date = None
        max_date = None
        today = date_obj.today()

        for product_supplier in product.product_suppliers:
            supply_date, next_supply_date = product_supplier_obj.\
                    compute_supply_date(product_supplier, date=today)
            if (not min_date) or supply_date < min_date:
                min_date = supply_date
            if (not max_date):
                max_date = next_supply_date
            if supply_date > min_date and supply_date < max_date:
                max_date = supply_date
            if next_supply_date < max_date:
                max_date = next_supply_date

        if not min_date:
            min_date = datetime.date.max
            max_date = datetime.date.max

        return (min_date, max_date)

    def compute_request(self,
                        product,
                        location_id,
                        shortage_date,
                        product_quantity,
                        company,
                        order_point=None):
        """
        Return the value of the purchase request which will answer to
        the needed quantity at the given date. I.e: the latest
        purchase date, the expected supply date and the prefered
        supplier.
        """
        uom_obj = self.pool.get('product.uom')
        product_supplier_obj = self.pool.get('purchase.product_supplier')
        date_obj = self.pool.get('ir.date')

        supplier = None
        timedelta = datetime.timedelta.max
        today = date_obj.today()
        max_quantity = order_point and order_point.max_quantity or 0.0
        for product_supplier in product.product_suppliers:
            supply_date = product_supplier_obj.compute_supply_date(
                product_supplier, date=today)[0]
            sup_timedelta = shortage_date - supply_date
            if not supplier:
                supplier = product_supplier.party
                timedelta = sup_timedelta
                continue

            if timedelta < datetime.timedelta(0) \
                    and (sup_timedelta >= datetime.timedelta(0) \
                    or sup_timedelta > timedelta):
                supplier = product_supplier.party
                timedelta = sup_timedelta

        if supplier:
            purchase_date = product_supplier_obj.compute_purchase_date(
                product_supplier, shortage_date)
        else:
            purchase_date = today

        quantity = uom_obj.compute_qty(
            product.default_uom, max_quantity - product_quantity,
            product.purchase_uom or product.default_uom)

        if order_point:
            origin = 'stock.order_point,%s' % order_point.id
        else:
            origin = 'stock.order_point,0'
        return {
            'product': product,
            'party': supplier and supplier or None,
            'quantity': quantity,
            'uom': product.purchase_uom or product.default_uom,
            'computed_quantity': quantity,
            'computed_uom': product.purchase_uom or product.default_uom,
            'purchase_date': purchase_date,
            'supply_date': shortage_date,
            'stock_level': product_quantity,
            'company': company,
            'warehouse': location_id,
            'origin': origin,
        }

    def get_shortage(self,
                     location_id,
                     product_id,
                     min_date,
                     max_date,
                     min_date_qty,
                     order_point=None):
        """
        Return between min_date and max_date  the first date where the
            stock quantity is less than the minimal quantity and
            the smallest stock quantity in the interval
            or None if there is no date where stock quantity is less than
            the minimal quantity
        The minimal quantity comes from the order_point or is zero

        :param location_id: the stock location id
        :param produc_id: the product id
        :param min_date: the minimal date
        :param max_date: the maximal date
        :param min_date_qty: the stock quantity at the minimal date
        :param order_point: a BrowseRecord of the Order Point
        :return: a tuple with the date and the quantity
        """
        product_obj = self.pool.get('product.product')

        res_date = None
        res_qty = None

        min_quantity = order_point and order_point.min_quantity or 0.0

        current_date = min_date
        current_qty = min_date_qty
        while (current_date < max_date) or (current_date == min_date):
            if current_qty < min_quantity:
                if not res_date:
                    res_date = current_date
                if (not res_qty) or (current_qty < res_qty):
                    res_qty = current_qty

            with Transaction().set_context(stock_date_start=current_date,
                                           stock_date_end=current_date):
                res = product_obj.products_by_location([location_id],
                                                       [product_id],
                                                       with_childs=True,
                                                       skip_zero=False)
            for qty in res.itervalues():
                current_qty += qty
            if current_date == datetime.date.max:
                break
            current_date += datetime.timedelta(1)

        return (res_date, res_qty)

    def create(self, vals):
        for field_name in ('product', 'quantity', 'uom', 'warehouse',
                           'company'):
            if not vals.get(field_name):
                self.raise_user_error('create_request')
        return super(PurchaseRequest, self).create(vals)
Пример #18
0
class OrderPoint(ModelSQL, ModelView):
    """
    Order Point: Provide a way to define a supply policy for each
    product on each locations. Order points on warehouse are
    considered by the supply scheduler to generate purchase requests.
    """
    _name = 'stock.order_point'
    _description = "Order Point"

    product = fields.Many2One('product.product',
                              'Product',
                              required=True,
                              select=1,
                              domain=[
                                  ('type', '=', 'stockable'),
                                  ('purchasable', 'in',
                                   If(Equal(Eval('type'), 'purchase'), [True],
                                      [True, False])),
                              ],
                              on_change=['product'])
    warehouse_location = fields.Many2One('stock.location',
                                         'Warehouse Location',
                                         select=1,
                                         domain=[('type', '=', 'warehouse')],
                                         states={
                                             'invisible':
                                             Not(
                                                 Equal(Eval('type'),
                                                       'purchase')),
                                             'required':
                                             Equal(Eval('type'), 'purchase'),
                                         })
    storage_location = fields.Many2One('stock.location',
                                       'Storage Location',
                                       select=1,
                                       domain=[('type', '=', 'storage')],
                                       states={
                                           'invisible':
                                           Not(Equal(Eval('type'),
                                                     'internal')),
                                           'required':
                                           Equal(Eval('type'), 'internal'),
                                       })
    location = fields.Function(fields.Many2One('stock.location', 'Location'),
                               'get_location',
                               searcher='search_location')
    provisioning_location = fields.Many2One(
        'stock.location',
        'Provisioning Location',
        domain=[('type', '=', 'storage')],
        states={
            'invisible': Not(Equal(Eval('type'), 'internal')),
            'required': Equal(Eval('type'), 'internal'),
        })
    type = fields.Selection([('internal', 'Internal'),
                             ('purchase', 'Purchase')],
                            'Type',
                            select=1,
                            required=True)
    min_quantity = fields.Float('Minimal Quantity',
                                required=True,
                                digits=(16, Eval('unit_digits', 2)),
                                depends=['unit_digits'])
    max_quantity = fields.Float('Maximal Quantity',
                                required=True,
                                digits=(16, Eval('unit_digits', 2)),
                                depends=['unit_digits'])
    company = fields.Many2One('company.company',
                              'Company',
                              required=True,
                              domain=[
                                  ('id',
                                   If(In('company', Eval('context', {})), '=',
                                      '!='),
                                   Get(Eval('context', {}), 'company', 0)),
                              ])
    unit = fields.Function(fields.Many2One('product.uom', 'Unit'), 'get_unit')
    unit_digits = fields.Function(fields.Integer('Unit Digits'),
                                  'get_unit_digits')

    def __init__(self):
        super(OrderPoint, self).__init__()
        self._constraints += [
            ('check_concurrent_internal', 'concurrent_internal_op'),
            ('check_uniqueness', 'unique_op'),
        ]
        self._sql_constraints += [
            ('check_min_max_quantity',
             'CHECK( max_quantity is null or min_quantity is null or max_quantity >= min_quantity )',
             'Maximal quantity must be bigger than Minimal quantity'),
        ]
        self._error_messages.update(
            {'unique_op': 'Only one order point is allowed '\
                 'for each product-location pair.',
             'concurrent_internal_op': 'You can not define two order points '\
                 'on the same product with opposite locations.',})

    def default_type(self):
        return "purchase"

    def on_change_product(self, vals):
        product_obj = self.pool.get('product.product')
        res = {
            'unit': False,
            'unit.rec_name': '',
            'unit_digits': 2,
        }
        if vals.get('product'):
            product = product_obj.browse(vals['product'])
            res['unit'] = product.default_uom.id
            res['unit.rec_name'] = product.default_uom.rec_name
            res['unit_digits'] = product.default_uom.digits
        return res

    def get_unit(self, ids, name):
        res = {}
        for order in self.browse(ids):
            res[order.id] = order.product.default_uom.id
        return res

    def get_unit_digits(self, ids, name):
        res = {}
        for order in self.browse(ids):
            res[order.id] = order.product.default_uom.digits
        return res

    def check_concurrent_internal(self, ids):
        """
        Ensure that there is no 'concurrent' internal order
        points. I.E. no two order point with opposite location for the
        same product and same company.
        """
        internal_ids = self.search([
            ('id', 'in', ids),
            ('type', '=', 'internal'),
        ])
        if not internal_ids:
            return True

        query = ['OR']
        for op in self.browse(internal_ids):
            arg = [
                'AND', ('provisioning_location', '=', op.storage_location.id),
                ('storage_location', '=', op.provisioning_location.id),
                ('company', '=', op.company.id), ('type', '=', 'internal')
            ]
            query.append(arg)
        ids = self.search(query)
        return not bool(ids)

    def _type2field(self, type=None):
        t2f = {
            'purchase': 'warehouse_location',
            'internal': 'storage_location',
        }
        if type == None:
            return t2f
        else:
            return t2f[type]

    def check_uniqueness(self, ids):
        """
        Ensure uniqueness of order points. I.E that there is no several
        order point for the same location, the same product and the
        same company.
        """
        query = ['OR']
        for op in self.browse(ids):
            field = self._type2field(op.type)
            arg = [
                'AND',
                ('product', '=', op.product.id),
                (field, '=', op[field].id),
                ('id', '!=', op.id),
                ('company', '=', op.company.id),
            ]
            query.append(arg)
        ids = self.search(query)
        return not bool(ids)

    def get_rec_name(self, ids, name):
        if not ids:
            return {}
        if isinstance(ids, (int, long)):
            ids = [ids]
        res = {}
        for op in self.browse(ids):
            res[op.id] = "%s@%s" % (op.product.name, op.location.name)
        return res

    def search_rec_name(self, name, clause):
        res = []
        names = clause[2].split('@', 1)
        res.append(('product.template.name', clause[1], names[0]))
        if len(names) != 1 and names[1]:
            res.append(('location', clause[1], names[1]))
        return res

    def get_location(self, ids, name):
        location_obj = self.pool.get('stock.location')
        res = {}
        for op in self.browse(ids):
            if op.type == 'purchase':
                res[op.id] = op.warehouse_location.id
            elif op.type == 'internal':
                res[op.id] = op.storage_location.id
            else:
                res[op.id] = False
        return res

    def search_location(self, name, domain=None):
        ids = []
        for type, field in self._type2field().iteritems():
            args = [('type', '=', type)]
            for _, operator, operand in domain:
                args.append((field, operator, operand))
            ids.extend(self.search(args))
        return [('id', 'in', ids)]

    def default_company(self):
        return Transaction().context.get('company') or False
Пример #19
0
class SaleOpportunity(Workflow, ModelSQL, ModelView):
    'Sale Opportunity'
    __name__ = "sale.opportunity"
    _history = True
    _rec_name = 'reference'
    reference = fields.Char('Reference',
                            readonly=True,
                            required=True,
                            select=True)
    party = fields.Many2One(
        'party.party',
        'Party',
        select=True,
        on_change=['party'],
        states={
            'readonly': Eval('state').in_(['converted', 'lost', 'cancelled']),
            'required': ~Eval('state').in_(['lead', 'lost', 'cancelled']),
        },
        depends=['state'])
    address = fields.Many2One('party.address',
                              'Address',
                              domain=[('party', '=', Eval('party'))],
                              select=True,
                              depends=['party', 'state'],
                              states=_STATES_STOP)
    company = fields.Many2One('company.company',
                              'Company',
                              required=True,
                              select=True,
                              states=_STATES_STOP,
                              domain=[
                                  ('id',
                                   If(In('company', Eval('context', {})), '=',
                                      '!='),
                                   Get(Eval('context', {}), 'company', 0)),
                              ],
                              on_change=['company'],
                              depends=_DEPENDS_STOP)
    currency = fields.Function(
        fields.Many2One('currency.currency', 'Currency'), 'get_currency')
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'get_currency_digits')
    amount = fields.Numeric('Amount',
                            digits=(16, Eval('currency_digits', 2)),
                            depends=['currency_digits'],
                            help='Estimated revenue amount')
    payment_term = fields.Many2One('account.invoice.payment_term',
                                   'Payment Term',
                                   states={
                                       'required':
                                       Eval('state') == 'converted',
                                       'readonly':
                                       In(Eval('state'),
                                          ['converted', 'lost', 'cancelled']),
                                   },
                                   depends=['state'])
    employee = fields.Many2One('company.employee',
                               'Employee',
                               required=True,
                               states=_STATES_STOP,
                               depends=['state', 'company'],
                               domain=[('company', '=', Eval('company'))])
    start_date = fields.Date('Start Date',
                             required=True,
                             select=True,
                             states=_STATES_START,
                             depends=_DEPENDS_START)
    end_date = fields.Date('End Date',
                           select=True,
                           readonly=True,
                           states={
                               'invisible':
                               Not(
                                   In(Eval('state'),
                                      ['converted', 'cancelled', 'lost'])),
                           },
                           depends=['state'])
    description = fields.Char('Description',
                              required=True,
                              states=_STATES_STOP,
                              depends=_DEPENDS_STOP)
    comment = fields.Text('Comment',
                          states=_STATES_STOP,
                          depends=_DEPENDS_STOP)
    lines = fields.One2Many('sale.opportunity.line',
                            'opportunity',
                            'Lines',
                            states=_STATES_STOP,
                            depends=_DEPENDS_STOP)
    state = fields.Selection(STATES,
                             'State',
                             required=True,
                             select=True,
                             sort=False,
                             readonly=True)
    probability = fields.Integer('Conversion Probability',
                                 required=True,
                                 states={
                                     'readonly':
                                     Not(
                                         In(Eval('state'),
                                            ['opportunity', 'lead'])),
                                 },
                                 depends=['state'],
                                 help="Percentage between 0 and 100")
    history = fields.One2Many('sale.opportunity.history',
                              'opportunity',
                              'History',
                              readonly=True)
    lost_reason = fields.Text('Reason for loss',
                              states={
                                  'invisible': Eval('state') != 'lost',
                              },
                              depends=['state'])
    sale = fields.Many2One('sale.sale',
                           'Sale',
                           readonly=True,
                           states={
                               'invisible': Eval('state') != 'converted',
                           },
                           depends=['state'])

    @classmethod
    def __register__(cls, module_name):
        cursor = Transaction().cursor
        reference_exists = True
        if TableHandler.table_exist(cursor, cls._table):
            table = TableHandler(cursor, cls, module_name)
            reference_exists = table.column_exist('reference')
        super(SaleOpportunity, cls).__register__(module_name)
        table = TableHandler(cursor, cls, module_name)

        # Migration from 2.8: make party not required and add reference as
        # required
        table.not_null_action('party', action='remove')
        if not reference_exists:
            cursor.execute('UPDATE "' + cls._table +
                           '" SET reference=id WHERE '
                           'reference IS NULL')
            table.not_null_action('reference', action='add')

    @classmethod
    def __setup__(cls):
        super(SaleOpportunity, cls).__setup__()
        cls._order.insert(0, ('start_date', 'DESC'))
        cls._sql_constraints += [
            ('check_percentage',
             'CHECK(probability >= 0 AND probability <= 100)',
             'Probability must be between 0 and 100.')
        ]
        cls._error_messages.update({
            'delete_cancel': ('Sale Opportunity "%s" must be cancelled '
                              'before deletion.'),
        })
        cls._transitions |= set((
            ('lead', 'opportunity'),
            ('lead', 'lost'),
            ('lead', 'cancelled'),
            ('opportunity', 'converted'),
            ('opportunity', 'lead'),
            ('opportunity', 'lost'),
            ('opportunity', 'cancelled'),
            ('lost', 'lead'),
            ('cancelled', 'lead'),
        ))
        cls._buttons.update({
            'lead': {
                'invisible':
                ~Eval('state').in_(['cancelled', 'lost', 'opportunity']),
                'icon':
                If(
                    Eval('state').in_(['cancelled', 'lost']), 'tryton-clear',
                    'tryton-go-previous'),
            },
            'opportunity': {
                'invisible': ~Eval('state').in_(['lead']),
            },
            'convert': {
                'invisible': ~Eval('state').in_(['opportunity']),
            },
            'lost': {
                'invisible': ~Eval('state').in_(['lead', 'opportunity']),
            },
            'cancel': {
                'invisible': ~Eval('state').in_(['lead', 'opportunity']),
            },
        })

    @staticmethod
    def default_state():
        return 'lead'

    @staticmethod
    def default_start_date():
        Date = Pool().get('ir.date')
        return Date.today()

    @staticmethod
    def default_probability():
        return 50

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @staticmethod
    def default_employee():
        User = Pool().get('res.user')

        if Transaction().context.get('employee'):
            return Transaction().context['employee']
        else:
            user = User(Transaction().user)
            if user.employee:
                return user.employee.id

    @classmethod
    def default_payment_term(cls):
        PaymentTerm = Pool().get('account.invoice.payment_term')
        payment_terms = PaymentTerm.search(cls.payment_term.domain)
        if len(payment_terms) == 1:
            return payment_terms[0].id

    @classmethod
    def create(cls, vlist):
        pool = Pool()
        Sequence = pool.get('ir.sequence')
        Config = pool.get('sale.configuration')

        sequence = Config(1).sale_opportunity_sequence
        vlist = [x.copy() for x in vlist]
        for vals in vlist:
            vals['reference'] = Sequence.get_id(sequence.id)
        return super(SaleOpportunity, cls).create(vlist)

    @classmethod
    def copy(cls, opportunities, default=None):
        if default is None:
            default = {}
        default = default.copy()
        default.setdefault('reference', None)
        default.setdefault('history', None)
        return super(SaleOpportunity, cls).copy(opportunities, default=default)

    def get_currency(self, name):
        return self.company.currency.id

    def get_currency_digits(self, name):
        return self.company.currency.digits

    def on_change_company(self):
        res = {}
        if self.company:
            res['currency'] = self.company.currency.id
            res['currency.rec_name'] = self.company.currency.rec_name
            res['currency_digits'] = self.company.currency.digits
        return res

    def on_change_party(self):
        PaymentTerm = Pool().get('account.invoice.payment_term')

        res = {
            'payment_term': None,
        }
        if self.party:
            if self.party.customer_payment_term:
                res['payment_term'] = self.party.customer_payment_term.id
                res['payment_term.rec_name'] = \
                    self.party.customer_payment_term.rec_name
        if not res['payment_term']:
            res['payment_term'] = self.default_payment_term()
            if res['payment_term']:
                res['payment_term.rec_name'] = PaymentTerm(
                    res['payment_term']).rec_name
        return res

    def _get_sale_line_opportunity_line(self, sale):
        '''
        Return sale lines for each opportunity line
        '''
        res = {}
        for line in self.lines:
            if line.sale_line:
                continue
            sale_line = line.get_sale_line(sale)
            if sale_line:
                res[line.id] = sale_line
        return res

    def _get_sale_opportunity(self):
        '''
        Return sale for an opportunity
        '''
        Sale = Pool().get('sale.sale')
        with Transaction().set_user(0, set_context=True):
            return Sale(
                description=self.description,
                party=self.party,
                payment_term=self.payment_term,
                company=self.company,
                invoice_address=self.address,
                shipment_address=self.address,
                currency=self.company.currency,
                comment=self.comment,
                sale_date=None,
            )

    def create_sale(self):
        '''
        Create a sale for the opportunity and return the sale
        '''
        Line = Pool().get('sale.opportunity.line')

        if self.sale:
            return

        sale = self._get_sale_opportunity()
        sale_lines = self._get_sale_line_opportunity_line(sale)
        sale.save()

        for line_id, sale_line in sale_lines.iteritems():
            sale_line.sale = sale
            sale_line.save()
            Line.write([Line(line_id)], {
                'sale_line': sale_line.id,
            })

        self.write([self], {
            'sale': sale.id,
        })
        return sale

    @classmethod
    def delete(cls, opportunities):
        # Cancel before delete
        cls.cancel(opportunities)
        for opportunity in opportunities:
            if opportunity.state != 'cancelled':
                cls.raise_user_error('delete_cancel', opportunity.rec_name)
        super(SaleOpportunity, cls).delete(opportunities)

    @classmethod
    @ModelView.button
    @Workflow.transition('lead')
    def lead(cls, opportunities):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('opportunity')
    def opportunity(cls, opportunities):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('converted')
    def convert(cls, opportunities):
        Date = Pool().get('ir.date')
        cls.write(opportunities, {
            'end_date': Date.today(),
        })
        for opportunity in opportunities:
            opportunity.create_sale()

    @classmethod
    @ModelView.button
    @Workflow.transition('lost')
    def lost(cls, opportunities):
        Date = Pool().get('ir.date')
        cls.write(opportunities, {
            'end_date': Date.today(),
        })

    @classmethod
    @ModelView.button
    @Workflow.transition('cancelled')
    def cancel(cls, opportunities):
        Date = Pool().get('ir.date')
        cls.write(opportunities, {
            'end_date': Date.today(),
        })