예제 #1
0
class FloatModel(models.Model):
    _name = name('float')
    _description = 'Tests: Base Import Model Float'

    value = fields.Float()
    value2 = fields.Monetary()
    currency_id = fields.Many2one('res.currency')
예제 #2
0
class Company(models.Model):
    _inherit = 'res.company'

    po_lead = fields.Float(
        string='Purchase Lead Time',
        required=True,
        help="Margin of error for vendor lead times. When the system "
        "generates Purchase Orders for procuring products, "
        "they will be scheduled that many days earlier "
        "to cope with unexpected vendor delays.",
        default=0.0)

    po_lock = fields.Selection(
        [('edit', 'Allow to edit purchase orders'),
         ('lock', 'Confirmed purchase orders are not editable')],
        string="Purchase Order Modification",
        default="edit",
        help=
        'Purchase Order Modification used when you want to purchase order editable after confirm'
    )

    po_double_validation = fields.Selection(
        [('one_step', 'Confirm purchase orders in one step'),
         ('two_step', 'Get 2 levels of approvals to confirm a purchase order')
         ],
        string="Levels of Approvals",
        default='one_step',
        help="Provide a double validation mechanism for purchases")

    po_double_validation_amount = fields.Monetary(
        string='Double validation amount',
        default=5000,
        help="Minimum amount for which a double validation is required")
예제 #3
0
파일: digest.py 프로젝트: metricsw/swerp
class Digest(models.Model):
    _inherit = 'digest.digest'

    kpi_all_sale_total = fields.Boolean('All Sales')
    kpi_all_sale_total_value = fields.Monetary(
        compute='_compute_kpi_sale_total_value')

    def _compute_kpi_sale_total_value(self):
        if not self.env.user.has_group(
                'sales_team.group_sale_salesman_all_leads'):
            raise AccessError(
                _("Do not have access, skip this data for user's digest email")
            )
        for record in self:
            start, end, company = record._get_kpi_compute_parameters()
            all_channels_sales = self.env['sale.report'].read_group(
                [('confirmation_date', '>=', start),
                 ('confirmation_date', '<', end),
                 ('company_id', '=', company.id)], ['price_total'],
                ['price_total'])
            record.kpi_all_sale_total_value = sum([
                channel_sale['price_total']
                for channel_sale in all_channels_sales
            ])

    def compute_kpis_actions(self, company, user):
        res = super(Digest, self).compute_kpis_actions(company, user)
        res['kpi_all_sale_total'] = 'sale.report_all_channels_sales_action&menu_id=%s' % self.env.ref(
            'sale.sale_menu_root').id
        return res
예제 #4
0
class Digest(models.Model):
    _inherit = 'digest.digest'

    kpi_website_sale_total = fields.Boolean('eCommerce Sales')
    kpi_website_sale_total_value = fields.Monetary(
        compute='_compute_kpi_website_sale_total_value')

    def _compute_kpi_website_sale_total_value(self):
        if not self.env.user.has_group(
                'sales_team.group_sale_salesman_all_leads'):
            raise AccessError(
                _("Do not have access, skip this data for user's digest email")
            )
        for record in self:
            start, end, company = record._get_kpi_compute_parameters()
            confirmed_website_sales = self.env['sale.order'].search([
                ('confirmation_date', '>=', start),
                ('confirmation_date', '<', end),
                ('state', 'not in', ['draft', 'cancel', 'sent']),
                ('team_id.team_type', '=', 'website'),
                ('company_id', '=', company.id)
            ])
            record.kpi_website_sale_total_value = sum(
                confirmed_website_sales.mapped('amount_total'))

    def compute_kpis_actions(self, company, user):
        res = super(Digest, self).compute_kpis_actions(company, user)
        res['kpi_website_sale_total'] = 'website.backend_dashboard&menu_id=%s' % self.env.ref(
            'website.menu_website_configuration').id
        return res
예제 #5
0
class AccountAnalyticLine(models.Model):
    _name = 'account.analytic.line'
    _description = 'Analytic Line'
    _order = 'date desc, id desc'

    @api.model
    def _default_user(self):
        return self.env.context.get('user_id', self.env.user.id)

    name = fields.Char('Description', required=True)
    date = fields.Date('Date',
                       required=True,
                       index=True,
                       default=fields.Date.context_today)
    amount = fields.Monetary('Amount', required=True, default=0.0)
    unit_amount = fields.Float('Quantity', default=0.0)
    product_uom_id = fields.Many2one('uom.uom', string='Unit of Measure')
    account_id = fields.Many2one('account.analytic.account',
                                 'Analytic Account',
                                 required=True,
                                 ondelete='restrict',
                                 index=True)
    partner_id = fields.Many2one('res.partner', string='Partner')
    user_id = fields.Many2one('res.users',
                              string='User',
                              default=_default_user)
    tag_ids = fields.Many2many('account.analytic.tag',
                               'account_analytic_line_tag_rel',
                               'line_id',
                               'tag_id',
                               string='Tags',
                               copy=True)
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 required=True,
                                 readonly=True,
                                 default=lambda self: self.env.user.company_id)
    currency_id = fields.Many2one(related="company_id.currency_id",
                                  string="Currency",
                                  readonly=True,
                                  store=True,
                                  compute_sudo=True)
    group_id = fields.Many2one('account.analytic.group',
                               related='account_id.group_id',
                               store=True,
                               readonly=True,
                               compute_sudo=True)

    @api.multi
    @api.constrains('company_id', 'account_id')
    def _check_company_id(self):
        for line in self:
            if line.account_id.company_id and line.company_id.id != line.account_id.company_id.id:
                raise ValidationError(
                    _('The selected account belongs to another company that the one you\'re trying to create an analytic item for'
                      ))
예제 #6
0
class ComplexModel(models.Model):
    _name = name('complex')
    _description = 'Tests: Base Import Model Complex'

    f = fields.Float()
    m = fields.Monetary()
    c = fields.Char()
    currency_id = fields.Many2one('res.currency')
    d = fields.Date()
    dt = fields.Datetime()
예제 #7
0
class SaleOrder(models.Model):
    _inherit = "sale.order"

    margin = fields.Monetary(compute='_product_margin', help="It gives profitability by calculating the difference between the Unit Price and the cost.", currency_field='currency_id', digits=dp.get_precision('Product Price'), store=True)

    @api.depends('order_line.margin')
    def _product_margin(self):
        if self.env.in_onchange:
            for order in self:
                order.margin = sum(order.order_line.filtered(lambda r: r.state != 'cancel').mapped('margin'))
        else:
            # On batch records recomputation (e.g. at install), compute the margins
            # with a single read_group query for better performance.
            # This isn't done in an onchange environment because (part of) the data
            # may not be stored in database (new records or unsaved modifications).
            grouped_order_lines_data = self.env['sale.order.line'].read_group(
                [
                    ('order_id', 'in', self.ids),
                    ('state', '!=', 'cancel'),
                ], ['margin', 'order_id'], ['order_id'])
            for data in grouped_order_lines_data:
                order = self.browse(data['order_id'][0])
                order.margin = data['margin']
예제 #8
0
class AccountBankStatementLine(models.Model):
    _name = "account.bank.statement.line"
    _description = "Bank Statement Line"
    _order = "statement_id desc, date, sequence, id desc"

    name = fields.Char(string='Label', required=True)
    date = fields.Date(required=True,
                       default=lambda self: self._context.get(
                           'date', fields.Date.context_today(self)))
    amount = fields.Monetary(digits=0, currency_field='journal_currency_id')
    journal_currency_id = fields.Many2one(
        'res.currency',
        string="Journal's Currency",
        related='statement_id.currency_id',
        help='Utility field to express amount currency',
        readonly=True)
    partner_id = fields.Many2one('res.partner', string='Partner')
    account_number = fields.Char(
        string='Bank Account Number',
        help=
        "Technical field used to store the bank account number before its creation, upon the line's processing"
    )
    bank_account_id = fields.Many2one(
        'res.partner.bank',
        string='Bank Account',
        help="Bank account that was used in this transaction.")
    account_id = fields.Many2one(
        'account.account',
        string='Counterpart Account',
        domain=[('deprecated', '=', False)],
        help=
        "This technical field can be used at the statement line creation/import time in order to avoid the reconciliation"
        " process on it later on. The statement line will simply create a counterpart on this account"
    )
    statement_id = fields.Many2one('account.bank.statement',
                                   string='Statement',
                                   index=True,
                                   required=True,
                                   ondelete='cascade')
    journal_id = fields.Many2one('account.journal',
                                 related='statement_id.journal_id',
                                 string='Journal',
                                 store=True,
                                 readonly=True)
    partner_name = fields.Char(
        help=
        "This field is used to record the third party name when importing bank statement in electronic format,"
        " when the partner doesn't exist yet in the database (or cannot be found)."
    )
    ref = fields.Char(string='Reference')
    note = fields.Text(string='Notes')
    sequence = fields.Integer(
        index=True,
        help=
        "Gives the sequence order when displaying a list of bank statement lines.",
        default=1)
    company_id = fields.Many2one('res.company',
                                 related='statement_id.company_id',
                                 string='Company',
                                 store=True,
                                 readonly=True)
    journal_entry_ids = fields.One2many('account.move.line',
                                        'statement_line_id',
                                        'Journal Items',
                                        copy=False,
                                        readonly=True)
    amount_currency = fields.Monetary(
        help=
        "The amount expressed in an optional other currency if it is a multi-currency entry."
    )
    currency_id = fields.Many2one(
        'res.currency',
        string='Currency',
        help="The optional other currency if it is a multi-currency entry.")
    state = fields.Selection(related='statement_id.state',
                             string='Status',
                             readonly=True)
    move_name = fields.Char(
        string='Journal Entry Name',
        readonly=True,
        default=False,
        copy=False,
        help=
        "Technical field holding the number given to the journal entry, automatically set when the statement line is reconciled then stored to set the same number again if the line is cancelled, set to draft and re-processed again."
    )

    @api.one
    @api.constrains('amount')
    def _check_amount(self):
        # Allow to enter bank statement line with an amount of 0,
        # so that user can enter/import the exact bank statement they have received from their bank in Swerp
        if self.journal_id.type != 'bank' and self.currency_id.is_zero(
                self.amount):
            raise ValidationError(
                _('The amount of a cash transaction cannot be 0.'))

    @api.one
    @api.constrains('amount', 'amount_currency')
    def _check_amount_currency(self):
        if self.amount_currency != 0 and self.amount == 0:
            raise ValidationError(
                _('If "Amount Currency" is specified, then "Amount" must be as well.'
                  ))

    @api.constrains('currency_id', 'journal_id')
    def _check_currency_id(self):
        for line in self:
            if not line.currency_id:
                continue

            statement_currency = line.journal_id.currency_id or line.company_id.currency_id
            if line.currency_id == statement_currency:
                raise ValidationError(
                    _('The currency of the bank statement line must be different than the statement currency.'
                      ))

    @api.model
    def create(self, vals):
        line = super(AccountBankStatementLine, self).create(vals)
        # The most awesome fix you will ever see is below.
        # Explanation: during a 'create', the 'convert_to_cache' method is not called. Moreover, at
        # that point 'journal_currency_id' is not yet known since it is a related field. It means
        # that the 'amount' field will not be properly rounded. The line below triggers a write on
        # the 'amount' field, which will trigger the 'convert_to_cache' method, and ultimately round
        # the field correctly.
        # This is obviously an awful workaround, but at the time of writing, the ORM does not
        # provide a clean mechanism to fix the issue.
        line.amount = line.amount
        return line

    @api.multi
    def unlink(self):
        for line in self:
            if line.journal_entry_ids.ids:
                raise UserError(
                    _('In order to delete a bank statement line, you must first cancel it to delete related journal items.'
                      ))
        return super(AccountBankStatementLine, self).unlink()

    @api.multi
    def button_cancel_reconciliation(self):
        aml_to_unbind = self.env['account.move.line']
        aml_to_cancel = self.env['account.move.line']
        payment_to_unreconcile = self.env['account.payment']
        payment_to_cancel = self.env['account.payment']
        for st_line in self:
            aml_to_unbind |= st_line.journal_entry_ids
            for line in st_line.journal_entry_ids:
                payment_to_unreconcile |= line.payment_id
                if st_line.move_name and line.payment_id.payment_reference == st_line.move_name:
                    #there can be several moves linked to a statement line but maximum one created by the line itself
                    aml_to_cancel |= line
                    payment_to_cancel |= line.payment_id
        aml_to_unbind = aml_to_unbind - aml_to_cancel

        if aml_to_unbind:
            aml_to_unbind.write({'statement_line_id': False})

        payment_to_unreconcile = payment_to_unreconcile - payment_to_cancel
        if payment_to_unreconcile:
            payment_to_unreconcile.unreconcile()

        if aml_to_cancel:
            aml_to_cancel.remove_move_reconcile()
            moves_to_cancel = aml_to_cancel.mapped('move_id')
            moves_to_cancel.button_cancel()
            moves_to_cancel.unlink()
        if payment_to_cancel:
            payment_to_cancel.unlink()

    ####################################################
    # Reconciliation methods
    ####################################################

    def _get_common_sql_query(self,
                              overlook_partner=False,
                              excluded_ids=None,
                              split=False):
        acc_type = "acc.reconcile = true"
        select_clause = "SELECT aml.id "
        from_clause = "FROM account_move_line aml JOIN account_account acc ON acc.id = aml.account_id "
        account_clause = ''
        if self.journal_id.default_credit_account_id and self.journal_id.default_debit_account_id:
            account_clause = "(aml.statement_id IS NULL AND aml.account_id IN %(account_payable_receivable)s AND aml.payment_id IS NOT NULL) OR"
        where_clause = """WHERE aml.company_id = %(company_id)s
                          AND (
                                    """ + account_clause + """
                                    (""" + acc_type + """ AND aml.reconciled = false)
                          )"""
        where_clause = where_clause + ' AND aml.partner_id = %(partner_id)s' if self.partner_id else where_clause
        where_clause = where_clause + ' AND aml.id NOT IN %(excluded_ids)s' if excluded_ids else where_clause
        if split:
            return select_clause, from_clause, where_clause
        return select_clause + from_clause + where_clause

    def _prepare_reconciliation_move(self, move_ref):
        """ Prepare the dict of values to create the move from a statement line. This method may be overridden to adapt domain logic
            through model inheritance (make sure to call super() to establish a clean extension chain).

           :param char move_ref: will be used as the reference of the generated account move
           :return: dict of value to create() the account.move
        """
        ref = move_ref or ''
        if self.ref:
            ref = move_ref + ' - ' + self.ref if move_ref else self.ref
        data = {
            'journal_id': self.statement_id.journal_id.id,
            'date': self.statement_id.accounting_date or self.date,
            'ref': ref,
        }
        if self.move_name:
            data.update(name=self.move_name)
        return data

    def _prepare_reconciliation_move_line(self, move, amount):
        """ Prepare the dict of values to balance the move.

            :param recordset move: the account.move to link the move line
            :param dict move: a dict of vals of a account.move which will be created later
            :param float amount: the amount of transaction that wasn't already reconciled
        """
        company_currency = self.journal_id.company_id.currency_id
        statement_currency = self.journal_id.currency_id or company_currency
        st_line_currency = self.currency_id or statement_currency
        amount_currency = False
        st_line_currency_rate = self.currency_id and (self.amount_currency /
                                                      self.amount) or False
        if isinstance(move, dict):
            amount_sum = sum(x[2].get('amount_currency', 0)
                             for x in move['line_ids'])
        else:
            amount_sum = sum(x.amount_currency for x in move.line_ids)
        # We have several use case here to compare the currency and amount currency of counterpart line to balance the move:
        if st_line_currency != company_currency and st_line_currency == statement_currency:
            # company in currency A, statement in currency B and transaction in currency B
            # counterpart line must have currency B and correct amount is inverse of already existing lines
            amount_currency = -amount_sum
        elif st_line_currency != company_currency and statement_currency == company_currency:
            # company in currency A, statement in currency A and transaction in currency B
            # counterpart line must have currency B and correct amount is inverse of already existing lines
            amount_currency = -amount_sum
        elif st_line_currency != company_currency and st_line_currency != statement_currency:
            # company in currency A, statement in currency B and transaction in currency C
            # counterpart line must have currency B and use rate between B and C to compute correct amount
            amount_currency = -amount_sum / st_line_currency_rate
        elif st_line_currency == company_currency and statement_currency != company_currency:
            # company in currency A, statement in currency B and transaction in currency A
            # counterpart line must have currency B and amount is computed using the rate between A and B
            amount_currency = amount / st_line_currency_rate

        # last case is company in currency A, statement in currency A and transaction in currency A
        # and in this case counterpart line does not need any second currency nor amount_currency

        aml_dict = {
            'name': self.name,
            'partner_id': self.partner_id and self.partner_id.id or False,
            'account_id': amount >= 0 \
                and self.statement_id.journal_id.default_credit_account_id.id \
                or self.statement_id.journal_id.default_debit_account_id.id,
            'credit': amount < 0 and -amount or 0.0,
            'debit': amount > 0 and amount or 0.0,
            'statement_line_id': self.id,
            'currency_id': statement_currency != company_currency and statement_currency.id or (st_line_currency != company_currency and st_line_currency.id or False),
            'amount_currency': amount_currency,
        }
        if isinstance(move, self.env['account.move'].__class__):
            aml_dict['move_id'] = move.id
        return aml_dict

    @api.multi
    def fast_counterpart_creation(self):
        """This function is called when confirming a bank statement and will allow to automatically process lines without
        going in the bank reconciliation widget. By setting an account_id on bank statement lines, it will create a journal
        entry using that account to counterpart the bank account
        """
        payment_list = []
        move_list = []
        account_type_receivable = self.env.ref(
            'account.data_account_type_receivable')
        already_done_stmt_line_ids = [
            a['statement_line_id'][0]
            for a in self.env['account.move.line'].read_group([(
                'statement_line_id', 'in',
                self.ids)], ['statement_line_id'], ['statement_line_id'])
        ]
        managed_st_line = []
        for st_line in self:
            # Technical functionality to automatically reconcile by creating a new move line
            if st_line.account_id and not st_line.id in already_done_stmt_line_ids:
                managed_st_line.append(st_line.id)
                # Create payment vals
                total = st_line.amount
                payment_methods = (
                    total > 0
                ) and st_line.journal_id.inbound_payment_method_ids or st_line.journal_id.outbound_payment_method_ids
                currency = st_line.journal_id.currency_id or st_line.company_id.currency_id
                partner_type = 'customer' if st_line.account_id.user_type_id == account_type_receivable else 'supplier'
                payment_list.append({
                    'payment_method_id':
                    payment_methods and payment_methods[0].id or False,
                    'payment_type':
                    total > 0 and 'inbound' or 'outbound',
                    'partner_id':
                    st_line.partner_id.id,
                    'partner_type':
                    partner_type,
                    'journal_id':
                    st_line.statement_id.journal_id.id,
                    'payment_date':
                    st_line.date,
                    'state':
                    'reconciled',
                    'currency_id':
                    currency.id,
                    'amount':
                    abs(total),
                    'communication':
                    st_line._get_communication(
                        payment_methods[0] if payment_methods else False),
                    'name':
                    st_line.statement_id.name
                    or _("Bank Statement %s") % st_line.date,
                })

                # Create move and move line vals
                move_vals = st_line._prepare_reconciliation_move(
                    st_line.statement_id.name)
                aml_dict = {
                    'name': st_line.name,
                    'debit': st_line.amount < 0 and -st_line.amount or 0.0,
                    'credit': st_line.amount > 0 and st_line.amount or 0.0,
                    'account_id': st_line.account_id.id,
                    'partner_id': st_line.partner_id.id,
                    'statement_line_id': st_line.id,
                }
                st_line._prepare_move_line_for_currency(
                    aml_dict, st_line.date or fields.Date.context_today())
                move_vals['line_ids'] = [(0, 0, aml_dict)]
                balance_line = self._prepare_reconciliation_move_line(
                    move_vals, -aml_dict['debit']
                    if st_line.amount < 0 else aml_dict['credit'])
                move_vals['line_ids'].append((0, 0, balance_line))
                move_list.append(move_vals)

        # Creates
        payment_ids = self.env['account.payment'].create(payment_list)
        for payment_id, move_vals in pycompat.izip(payment_ids, move_list):
            for line in move_vals['line_ids']:
                line[2]['payment_id'] = payment_id.id
        move_ids = self.env['account.move'].create(move_list)
        move_ids.post()

        for move, st_line, payment in pycompat.izip(
                move_ids, self.browse(managed_st_line), payment_ids):
            st_line.write({'move_name': move.name})
            payment.write({'payment_reference': move.name})

    def _get_communication(self, payment_method_id):
        return self.name or ''

    def process_reconciliation(self,
                               counterpart_aml_dicts=None,
                               payment_aml_rec=None,
                               new_aml_dicts=None):
        """ Match statement lines with existing payments (eg. checks) and/or payables/receivables (eg. invoices and credit notes) and/or new move lines (eg. write-offs).
            If any new journal item needs to be created (via new_aml_dicts or counterpart_aml_dicts), a new journal entry will be created and will contain those
            items, as well as a journal item for the bank statement line.
            Finally, mark the statement line as reconciled by putting the matched moves ids in the column journal_entry_ids.

            :param self: browse collection of records that are supposed to have no accounting entries already linked.
            :param (list of dicts) counterpart_aml_dicts: move lines to create to reconcile with existing payables/receivables.
                The expected keys are :
                - 'name'
                - 'debit'
                - 'credit'
                - 'move_line'
                    # The move line to reconcile (partially if specified debit/credit is lower than move line's credit/debit)

            :param (list of recordsets) payment_aml_rec: recordset move lines representing existing payments (which are already fully reconciled)

            :param (list of dicts) new_aml_dicts: move lines to create. The expected keys are :
                - 'name'
                - 'debit'
                - 'credit'
                - 'account_id'
                - (optional) 'tax_ids'
                - (optional) Other account.move.line fields like analytic_account_id or analytics_id

            :returns: The journal entries with which the transaction was matched. If there was at least an entry in counterpart_aml_dicts or new_aml_dicts, this list contains
                the move created by the reconciliation, containing entries for the statement.line (1), the counterpart move lines (0..*) and the new move lines (0..*).
        """
        payable_account_type = self.env.ref(
            'account.data_account_type_payable')
        receivable_account_type = self.env.ref(
            'account.data_account_type_receivable')
        counterpart_aml_dicts = counterpart_aml_dicts or []
        payment_aml_rec = payment_aml_rec or self.env['account.move.line']
        new_aml_dicts = new_aml_dicts or []

        aml_obj = self.env['account.move.line']

        company_currency = self.journal_id.company_id.currency_id
        statement_currency = self.journal_id.currency_id or company_currency
        st_line_currency = self.currency_id or statement_currency

        counterpart_moves = self.env['account.move']

        # Check and prepare received data
        if any(rec.statement_id for rec in payment_aml_rec):
            raise UserError(_('A selected move line was already reconciled.'))
        for aml_dict in counterpart_aml_dicts:
            if aml_dict['move_line'].reconciled:
                raise UserError(
                    _('A selected move line was already reconciled.'))
            if isinstance(aml_dict['move_line'], pycompat.integer_types):
                aml_dict['move_line'] = aml_obj.browse(aml_dict['move_line'])

        account_types = self.env['account.account.type']
        for aml_dict in (counterpart_aml_dicts + new_aml_dicts):
            if aml_dict.get('tax_ids') and isinstance(aml_dict['tax_ids'][0],
                                                      pycompat.integer_types):
                # Transform the value in the format required for One2many and Many2many fields
                aml_dict['tax_ids'] = [(4, id, None)
                                       for id in aml_dict['tax_ids']]

            user_type_id = self.env['account.account'].browse(
                aml_dict.get('account_id')).user_type_id
            if user_type_id in [payable_account_type, receivable_account_type
                                ] and user_type_id not in account_types:
                account_types |= user_type_id
        if any(line.journal_entry_ids for line in self):
            raise UserError(
                _('A selected statement line was already reconciled with an account move.'
                  ))

        # Fully reconciled moves are just linked to the bank statement
        total = self.amount
        currency = self.currency_id or statement_currency
        for aml_rec in payment_aml_rec:
            balance = aml_rec.amount_currency if aml_rec.currency_id else aml_rec.balance
            aml_currency = aml_rec.currency_id or aml_rec.company_currency_id
            total -= aml_currency._convert(balance, currency,
                                           aml_rec.company_id, aml_rec.date)
            aml_rec.with_context(check_move_validity=False).write(
                {'statement_line_id': self.id})
            counterpart_moves = (counterpart_moves | aml_rec.move_id)
            if aml_rec.journal_id.post_at_bank_rec and aml_rec.payment_id and aml_rec.move_id.state == 'draft':
                # In case the journal is set to only post payments when performing bank
                # reconciliation, we modify its date and post it.
                aml_rec.move_id.date = self.date
                aml_rec.payment_id.payment_date = self.date
                aml_rec.move_id.post()
                # We check the paid status of the invoices reconciled with this payment
                for invoice in aml_rec.payment_id.reconciled_invoice_ids:
                    self._check_invoice_state(invoice)

        # Create move line(s). Either matching an existing journal entry (eg. invoice), in which
        # case we reconcile the existing and the new move lines together, or being a write-off.
        if counterpart_aml_dicts or new_aml_dicts:

            # Create the move
            self.sequence = self.statement_id.line_ids.ids.index(self.id) + 1
            move_vals = self._prepare_reconciliation_move(
                self.statement_id.name)
            move = self.env['account.move'].create(move_vals)
            counterpart_moves = (counterpart_moves | move)

            # Create The payment
            payment = self.env['account.payment']
            partner_id = self.partner_id or (
                aml_dict.get('move_line') and
                aml_dict['move_line'].partner_id) or self.env['res.partner']
            if abs(total) > 0.00001:
                partner_type = False
                if partner_id and len(account_types) == 1:
                    partner_type = 'customer' if account_types == receivable_account_type else 'supplier'
                if partner_id and not partner_type:
                    if total < 0:
                        partner_type = 'supplier'
                    else:
                        partner_type = 'customer'

                payment_methods = (
                    total > 0
                ) and self.journal_id.inbound_payment_method_ids or self.journal_id.outbound_payment_method_ids
                currency = self.journal_id.currency_id or self.company_id.currency_id
                payment = self.env['account.payment'].create({
                    'payment_method_id':
                    payment_methods and payment_methods[0].id or False,
                    'payment_type':
                    total > 0 and 'inbound' or 'outbound',
                    'partner_id':
                    partner_id.id,
                    'partner_type':
                    partner_type,
                    'journal_id':
                    self.statement_id.journal_id.id,
                    'payment_date':
                    self.date,
                    'state':
                    'reconciled',
                    'currency_id':
                    currency.id,
                    'amount':
                    abs(total),
                    'communication':
                    self._get_communication(
                        payment_methods[0] if payment_methods else False),
                    'name':
                    self.statement_id.name
                    or _("Bank Statement %s") % self.date,
                })

            # Complete dicts to create both counterpart move lines and write-offs
            to_create = (counterpart_aml_dicts + new_aml_dicts)
            date = self.date or fields.Date.today()
            for aml_dict in to_create:
                aml_dict['move_id'] = move.id
                aml_dict['partner_id'] = self.partner_id.id
                aml_dict['statement_line_id'] = self.id
                self._prepare_move_line_for_currency(aml_dict, date)

            # Create write-offs
            for aml_dict in new_aml_dicts:
                aml_dict['payment_id'] = payment and payment.id or False
                aml_obj.with_context(
                    check_move_validity=False).create(aml_dict)

            # Create counterpart move lines and reconcile them
            for aml_dict in counterpart_aml_dicts:
                if aml_dict['move_line'].payment_id and not aml_dict[
                        'move_line'].statement_line_id:
                    aml_dict['move_line'].write({'statement_line_id': self.id})
                if aml_dict['move_line'].partner_id.id:
                    aml_dict['partner_id'] = aml_dict[
                        'move_line'].partner_id.id
                aml_dict['account_id'] = aml_dict['move_line'].account_id.id
                aml_dict['payment_id'] = payment and payment.id or False

                counterpart_move_line = aml_dict.pop('move_line')
                new_aml = aml_obj.with_context(
                    check_move_validity=False).create(aml_dict)

                (new_aml | counterpart_move_line).reconcile()

                self._check_invoice_state(counterpart_move_line.invoice_id)

            # Balance the move
            st_line_amount = -sum([x.balance for x in move.line_ids])
            aml_dict = self._prepare_reconciliation_move_line(
                move, st_line_amount)
            aml_dict['payment_id'] = payment and payment.id or False
            aml_obj.with_context(check_move_validity=False).create(aml_dict)

            move.update_lines_tax_exigibility(
            )  # Needs to be called manually as lines were created 1 by 1
            move.post()
            #record the move name on the statement line to be able to retrieve it in case of unreconciliation
            self.write({'move_name': move.name})
            payment and payment.write({'payment_reference': move.name})
        elif self.move_name:
            raise UserError(
                _('Operation not allowed. Since your statement line already received a number (%s), you cannot reconcile it entirely with existing journal entries otherwise it would make a gap in the numbering. You should book an entry and make a regular revert of it in case you want to cancel it.'
                  ) % (self.move_name))

        #create the res.partner.bank if needed
        if self.account_number and self.partner_id and not self.bank_account_id:
            # Search bank account without partner to handle the case the res.partner.bank already exists but is set
            # on a different partner.
            bank_account = self.env['res.partner.bank'].search([
                ('company_id', '=', self.company_id.id),
                ('acc_number', '=', self.account_number)
            ])
            if not bank_account:
                bank_account = self.env['res.partner.bank'].create({
                    'acc_number':
                    self.account_number,
                    'partner_id':
                    self.partner_id.id
                })
            self.bank_account_id = bank_account

        counterpart_moves.assert_balanced()
        return counterpart_moves

    @api.multi
    def _prepare_move_line_for_currency(self, aml_dict, date):
        self.ensure_one()
        company_currency = self.journal_id.company_id.currency_id
        statement_currency = self.journal_id.currency_id or company_currency
        st_line_currency = self.currency_id or statement_currency
        st_line_currency_rate = self.currency_id and (self.amount_currency /
                                                      self.amount) or False
        company = self.company_id

        if st_line_currency.id != company_currency.id:
            aml_dict[
                'amount_currency'] = aml_dict['debit'] - aml_dict['credit']
            aml_dict['currency_id'] = st_line_currency.id
            if self.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate:
                # Statement is in company currency but the transaction is in foreign currency
                aml_dict['debit'] = company_currency.round(
                    aml_dict['debit'] / st_line_currency_rate)
                aml_dict['credit'] = company_currency.round(
                    aml_dict['credit'] / st_line_currency_rate)
            elif self.currency_id and st_line_currency_rate:
                # Statement is in foreign currency and the transaction is in another one
                aml_dict['debit'] = statement_currency._convert(
                    aml_dict['debit'] / st_line_currency_rate,
                    company_currency, company, date)
                aml_dict['credit'] = statement_currency._convert(
                    aml_dict['credit'] / st_line_currency_rate,
                    company_currency, company, date)
            else:
                # Statement is in foreign currency and no extra currency is given for the transaction
                aml_dict['debit'] = st_line_currency._convert(
                    aml_dict['debit'], company_currency, company, date)
                aml_dict['credit'] = st_line_currency._convert(
                    aml_dict['credit'], company_currency, company, date)
        elif statement_currency.id != company_currency.id:
            # Statement is in foreign currency but the transaction is in company currency
            prorata_factor = (aml_dict['debit'] -
                              aml_dict['credit']) / self.amount_currency
            aml_dict['amount_currency'] = prorata_factor * self.amount
            aml_dict['currency_id'] = statement_currency.id

    def _check_invoice_state(self, invoice):
        if invoice.state == 'in_payment' and all([
                payment.state == 'reconciled' for payment in invoice.mapped(
                    'payment_move_line_ids.payment_id')
        ]):
            invoice.write({'state': 'paid'})
예제 #9
0
class ProductWishlist(models.Model):
    _name = 'product.wishlist'
    _description = 'Product Wishlist'
    _sql_constraints = [
        ("product_unique_partner_id",
         "UNIQUE(product_id, partner_id)",
         "Duplicated wishlisted product for this partner."),
    ]

    partner_id = fields.Many2one('res.partner', string='Owner')
    product_id = fields.Many2one('product.product', string='Product', required=True)
    currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', readonly=True)
    pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', help='Pricelist when added')
    price = fields.Monetary(digits=0, currency_field='currency_id', string='Price', help='Price of the product when it has been added in the wishlist')
    website_id = fields.Many2one('website', ondelete='cascade', required=True)
    active = fields.Boolean(default=True, required=True)

    @api.model
    def current(self):
        """Get all wishlist items that belong to current user or session,
        filter products that are unpublished."""
        if not request:
            return self

        if request.website.is_public_user():
            wish = self.sudo().search([('id', 'in', request.session.get('wishlist_ids', []))])
        else:
            wish = self.search([("partner_id", "=", self.env.user.partner_id.id)])

        return wish.filtered(lambda x: x.sudo().product_id.product_tmpl_id.website_published and x.sudo().product_id.product_tmpl_id.sale_ok)

    @api.model
    def _add_to_wishlist(self, pricelist_id, currency_id, website_id, price, product_id, partner_id=False):
        wish = self.env['product.wishlist'].create({
            'partner_id': partner_id,
            'product_id': product_id,
            'currency_id': currency_id,
            'pricelist_id': pricelist_id,
            'price': price,
            'website_id': website_id,
        })
        return wish

    @api.model
    def _check_wishlist_from_session(self):
        """Assign all wishlist withtout partner from this the current session"""
        session_wishes = self.sudo().search([('id', 'in', request.session.get('wishlist_ids', []))])
        partner_wishes = self.sudo().search([("partner_id", "=", self.env.user.partner_id.id)])
        partner_products = partner_wishes.mapped("product_id")
        # Remove session products already present for the user
        duplicated_wishes = session_wishes.filtered(lambda wish: wish.product_id <= partner_products)
        session_wishes -= duplicated_wishes
        duplicated_wishes.unlink()
        # Assign the rest to the user
        session_wishes.write({"partner_id": self.env.user.partner_id.id})
        request.session.pop('wishlist_ids')

    @api.model
    def _garbage_collector(self, *args, **kwargs):
        """Remove wishlists for unexisting sessions."""
        self.with_context(active_test=False).search([
            ("create_date", "<", fields.Datetime.to_string(datetime.now() - timedelta(weeks=kwargs.get('wishlist_week', 5)))),
            ("partner_id", "=", False),
        ]).unlink()
예제 #10
0
class AccountClosing(models.Model):
    """
    This object holds an interval total and a grand total of the accounts of type receivable for a company,
    as well as the last account_move that has been counted in a previous object
    It takes its earliest brother to infer from when the computation needs to be done
    in order to compute its own data.
    """
    _name = 'account.sale.closing'
    _order = 'date_closing_stop desc, sequence_number desc'
    _description = "Sale Closing"

    name = fields.Char(help="Frequency and unique sequence number",
                       required=True)
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 readonly=True,
                                 required=True)
    date_closing_stop = fields.Datetime(
        string="Closing Date",
        help='Date to which the values are computed',
        readonly=True,
        required=True)
    date_closing_start = fields.Datetime(
        string="Starting Date",
        help='Date from which the total interval is computed',
        readonly=True,
        required=True)
    frequency = fields.Selection(string='Closing Type',
                                 selection=[('daily', 'Daily'),
                                            ('monthly', 'Monthly'),
                                            ('annually', 'Annual')],
                                 readonly=True,
                                 required=True)
    total_interval = fields.Monetary(
        string="Period Total",
        help=
        'Total in receivable accounts during the interval, excluding overlapping periods',
        readonly=True,
        required=True)
    cumulative_total = fields.Monetary(
        string="Cumulative Grand Total",
        help='Total in receivable accounts since the beginnig of times',
        readonly=True,
        required=True)
    sequence_number = fields.Integer('Sequence #',
                                     readonly=True,
                                     required=True)
    last_move_id = fields.Many2one(
        'account.move',
        string='Last journal entry',
        help='Last Journal entry included in the grand total',
        readonly=True)
    last_move_hash = fields.Char(
        string='Last journal entry\'s inalteralbility hash', readonly=True)
    currency_id = fields.Many2one('res.currency',
                                  string='Currency',
                                  help="The company's currency",
                                  readonly=True,
                                  related='company_id.currency_id',
                                  store=True)

    def _query_for_aml(self, company, first_move_sequence_number, date_start):
        params = {'company_id': company.id}
        query = '''WITH aggregate AS (SELECT m.id AS move_id,
                    aml.balance AS balance,
                    aml.id as line_id
            FROM account_move_line aml
            JOIN account_journal j ON aml.journal_id = j.id
            JOIN account_account acc ON acc.id = aml.account_id
            JOIN account_account_type t ON (t.id = acc.user_type_id AND t.type = 'receivable')
            JOIN account_move m ON m.id = aml.move_id
            WHERE j.type = 'sale'
                AND aml.company_id = %(company_id)s
                AND m.state = 'posted' '''

        if first_move_sequence_number is not False and first_move_sequence_number is not None:
            params['first_move_sequence_number'] = first_move_sequence_number
            query += '''AND m.l10n_fr_secure_sequence_number > %(first_move_sequence_number)s'''
        elif date_start:
            #the first time we compute the closing, we consider only from the installation of the module
            params['date_start'] = date_start
            query += '''AND m.date >= %(date_start)s'''

        query += " ORDER BY m.l10n_fr_secure_sequence_number DESC) "
        query += '''SELECT array_agg(move_id) AS move_ids,
                           array_agg(line_id) AS line_ids,
                           sum(balance) AS balance
                    FROM aggregate'''

        self.env.cr.execute(query, params)
        return self.env.cr.dictfetchall()[0]

    def _compute_amounts(self, frequency, company):
        """
        Method used to compute all the business data of the new object.
        It will search for previous closings of the same frequency to infer the move from which
        account move lines should be fetched.
        @param {string} frequency: a valid value of the selection field on the object (daily, monthly, annually)
            frequencies are literal (daily means 24 hours and so on)
        @param {recordset} company: the company for which the closing is done
        @return {dict} containing {field: value} for each business field of the object
        """
        interval_dates = self._interval_dates(frequency, company)
        previous_closing = self.search([('frequency', '=', frequency),
                                        ('company_id', '=', company.id)],
                                       limit=1,
                                       order='sequence_number desc')

        first_move = self.env['account.move']
        date_start = interval_dates['interval_from']
        cumulative_total = 0
        if previous_closing:
            first_move = previous_closing.last_move_id
            date_start = previous_closing.create_date
            cumulative_total += previous_closing.cumulative_total

        aml_aggregate = self._query_for_aml(
            company, first_move.l10n_fr_secure_sequence_number, date_start)

        total_interval = aml_aggregate['balance'] or 0
        cumulative_total += total_interval

        # We keep the reference to avoid gaps (like daily object during the weekend)
        last_move = first_move
        if aml_aggregate['move_ids']:
            last_move = last_move.browse(aml_aggregate['move_ids'][0])

        return {
            'total_interval':
            total_interval,
            'cumulative_total':
            cumulative_total,
            'last_move_id':
            last_move.id,
            'last_move_hash':
            last_move.l10n_fr_hash,
            'date_closing_stop':
            interval_dates['date_stop'],
            'date_closing_start':
            date_start,
            'name':
            interval_dates['name_interval'] + ' - ' +
            interval_dates['date_stop'][:10]
        }

    def _interval_dates(self, frequency, company):
        """
        Method used to compute the theoretical date from which account move lines should be fetched
        @param {string} frequency: a valid value of the selection field on the object (daily, monthly, annually)
            frequencies are literal (daily means 24 hours and so on)
        @param {recordset} company: the company for which the closing is done
        @return {dict} the theoretical date from which account move lines are fetched.
            date_stop date to which the move lines are fetched, always now()
            the dates are in their Swerp Database string representation
        """
        date_stop = datetime.utcnow()
        interval_from = None
        name_interval = ''
        if frequency == 'daily':
            interval_from = date_stop - timedelta(days=1)
            name_interval = _('Daily Closing')
        elif frequency == 'monthly':
            month_target = date_stop.month > 1 and date_stop.month - 1 or 12
            year_target = month_target < 12 and date_stop.year or date_stop.year - 1
            interval_from = date_stop.replace(year=year_target,
                                              month=month_target)
            name_interval = _('Monthly Closing')
        elif frequency == 'annually':
            year_target = date_stop.year - 1
            interval_from = date_stop.replace(year=year_target)
            name_interval = _('Annual Closing')

        return {
            'interval_from': FieldDateTime.to_string(interval_from),
            'date_stop': FieldDateTime.to_string(date_stop),
            'name_interval': name_interval
        }

    @api.multi
    def write(self, vals):
        raise UserError(
            _('Sale Closings are not meant to be written or deleted under any circumstances.'
              ))

    @api.multi
    def unlink(self):
        raise UserError(
            _('Sale Closings are not meant to be written or deleted under any circumstances.'
              ))

    @api.model
    def _automated_closing(self, frequency='daily'):
        """To be executed by the CRON to create an object of the given frequency for each company that needs it
        @param {string} frequency: a valid value of the selection field on the object (daily, monthly, annually)
            frequencies are literal (daily means 24 hours and so on)
        @return {recordset} all the objects created for the given frequency
        """
        res_company = self.env['res.company'].search([])
        account_closings = self.env['account.sale.closing']
        for company in res_company.filtered(
                lambda c: c._is_accounting_unalterable()):
            new_sequence_number = company.l10n_fr_closing_sequence_id.next_by_id(
            )
            values = self._compute_amounts(frequency, company)
            values['frequency'] = frequency
            values['company_id'] = company.id
            values['sequence_number'] = new_sequence_number
            account_closings |= account_closings.create(values)

        return account_closings
예제 #11
0
class Contract(models.Model):

    _name = 'hr.contract'
    _description = 'Contract'
    _inherit = ['mail.thread', 'mail.activity.mixin']

    name = fields.Char('Contract Reference', required=True)
    active = fields.Boolean(default=True)
    employee_id = fields.Many2one('hr.employee', string='Employee')
    department_id = fields.Many2one('hr.department', string="Department")
    type_id = fields.Many2one('hr.contract.type', string="Employee Category", required=True, default=lambda self: self.env['hr.contract.type'].search([], limit=1))
    job_id = fields.Many2one('hr.job', string='Job Position')
    date_start = fields.Date('Start Date', required=True, default=fields.Date.today,
        help="Start date of the contract.")
    date_end = fields.Date('End Date',
        help="End date of the contract (if it's a fixed-term contract).")
    trial_date_end = fields.Date('End of Trial Period',
        help="End date of the trial period (if there is one).")
    resource_calendar_id = fields.Many2one(
        'resource.calendar', 'Working Schedule',
        default=lambda self: self.env['res.company']._company_default_get().resource_calendar_id.id)
    wage = fields.Monetary('Wage', digits=(16, 2), required=True, track_visibility="onchange", help="Employee's monthly gross wage.")
    advantages = fields.Text('Advantages')
    notes = fields.Text('Notes')
    state = fields.Selection([
        ('draft', 'New'),
        ('open', 'Running'),
        ('pending', 'To Renew'),
        ('close', 'Expired'),
        ('cancel', 'Cancelled')
    ], string='Status', group_expand='_expand_states',
       track_visibility='onchange', help='Status of the contract', default='draft')
    company_id = fields.Many2one('res.company', default=lambda self: self.env.user.company_id)
    currency_id = fields.Many2one(string="Currency", related='company_id.currency_id', readonly=True)
    permit_no = fields.Char('Work Permit No', related="employee_id.permit_no", readonly=False)
    visa_no = fields.Char('Visa No', related="employee_id.visa_no", readonly=False)
    visa_expire = fields.Date('Visa Expire Date', related="employee_id.visa_expire", readonly=False)
    reported_to_secretariat = fields.Boolean('Social Secretariat',
        help='Green this button when the contract information has been transfered to the social secretariat.')

    def _expand_states(self, states, domain, order):
        return [key for key, val in type(self).state.selection]

    @api.onchange('employee_id')
    def _onchange_employee_id(self):
        if self.employee_id:
            self.job_id = self.employee_id.job_id
            self.department_id = self.employee_id.department_id
            self.resource_calendar_id = self.employee_id.resource_calendar_id

    @api.constrains('date_start', 'date_end')
    def _check_dates(self):
        if self.filtered(lambda c: c.date_end and c.date_start > c.date_end):
            raise ValidationError(_('Contract start date must be earlier than contract end date.'))

    @api.model
    def update_state(self):
        self.search([
            ('state', '=', 'open'),
            '|',
            '&',
            ('date_end', '<=', fields.Date.to_string(date.today() + relativedelta(days=7))),
            ('date_end', '>=', fields.Date.to_string(date.today() + relativedelta(days=1))),
            '&',
            ('visa_expire', '<=', fields.Date.to_string(date.today() + relativedelta(days=60))),
            ('visa_expire', '>=', fields.Date.to_string(date.today() + relativedelta(days=1))),
        ]).write({
            'state': 'pending'
        })

        self.search([
            ('state', 'in', ('open', 'pending')),
            '|',
            ('date_end', '<=', fields.Date.to_string(date.today() + relativedelta(days=1))),
            ('visa_expire', '<=', fields.Date.to_string(date.today() + relativedelta(days=1))),
        ]).write({
            'state': 'close'
        })

        return True

    @api.multi
    def _track_subtype(self, init_values):
        self.ensure_one()
        if 'state' in init_values and self.state == 'pending':
            return 'hr_contract.mt_contract_pending'
        elif 'state' in init_values and self.state == 'close':
            return 'hr_contract.mt_contract_close'
        return super(Contract, self)._track_subtype(init_values)
예제 #12
0
class TaxAdjustments(models.TransientModel):
    _name = 'tax.adjustments.wizard'
    _description = 'Tax Adjustments Wizard'

    @api.multi
    def _get_default_journal(self):
        return self.env['account.journal'].search([('type', '=', 'general')],
                                                  limit=1).id

    reason = fields.Char(string='Justification', required=True)
    journal_id = fields.Many2one('account.journal',
                                 string='Journal',
                                 required=True,
                                 default=_get_default_journal,
                                 domain=[('type', '=', 'general')])
    date = fields.Date(required=True, default=fields.Date.context_today)
    debit_account_id = fields.Many2one('account.account',
                                       string='Debit account',
                                       required=True,
                                       domain=[('deprecated', '=', False)])
    credit_account_id = fields.Many2one('account.account',
                                        string='Credit account',
                                        required=True,
                                        domain=[('deprecated', '=', False)])
    amount = fields.Monetary(currency_field='company_currency_id',
                             required=True)
    adjustment_type = fields.Selection(
        [('debit', 'Applied on debit journal item'),
         ('credit', 'Applied on credit journal item')],
        string="Adjustment Type",
        store=False,
        required=True)
    company_currency_id = fields.Many2one(
        'res.currency',
        readonly=True,
        default=lambda self: self.env.user.company_id.currency_id)
    tax_id = fields.Many2one('account.tax',
                             string='Adjustment Tax',
                             ondelete='restrict',
                             domain=[('type_tax_use', '=', 'adjustment')],
                             required=True)

    @api.multi
    def _create_move(self):
        adjustment_type = self.env.context.get(
            'adjustment_type', (self.amount > 0.0 and 'debit' or 'credit'))
        debit_vals = {
            'name': self.reason,
            'debit': abs(self.amount),
            'credit': 0.0,
            'account_id': self.debit_account_id.id,
            'tax_line_id': adjustment_type == 'debit' and self.tax_id.id
            or False,
        }
        credit_vals = {
            'name': self.reason,
            'debit': 0.0,
            'credit': abs(self.amount),
            'account_id': self.credit_account_id.id,
            'tax_line_id': adjustment_type == 'credit' and self.tax_id.id
            or False,
        }
        vals = {
            'journal_id': self.journal_id.id,
            'date': self.date,
            'state': 'draft',
            'line_ids': [(0, 0, debit_vals), (0, 0, credit_vals)]
        }
        move = self.env['account.move'].create(vals)
        move.post()
        return move.id

    @api.multi
    def create_move_debit(self):
        return self.with_context(adjustment_type='debit').create_move()

    @api.multi
    def create_move_credit(self):
        return self.with_context(adjustment_type='credit').create_move()

    def create_move(self):
        #create the adjustment move
        move_id = self._create_move()
        #return an action showing the created move
        action = self.env.ref(
            self.env.context.get('action', 'account.action_move_line_form'))
        result = action.read()[0]
        result['views'] = [(False, 'form')]
        result['res_id'] = move_id
        return result
class HrExpenseSheetRegisterPaymentWizard(models.TransientModel):

    _name = "hr.expense.sheet.register.payment.wizard"
    _description = "Expense Register Payment Wizard"

    @api.model
    def _default_partner_id(self):
        context = dict(self._context or {})
        active_ids = context.get('active_ids', [])
        expense_sheet = self.env['hr.expense.sheet'].browse(active_ids)
        return expense_sheet.address_id.id or expense_sheet.employee_id.id and expense_sheet.employee_id.address_home_id.id

    partner_id = fields.Many2one('res.partner',
                                 string='Partner',
                                 required=True,
                                 default=_default_partner_id)
    partner_bank_account_id = fields.Many2one('res.partner.bank',
                                              string="Recipient Bank Account")
    journal_id = fields.Many2one('account.journal',
                                 string='Payment Method',
                                 required=True,
                                 domain=[('type', 'in', ('bank', 'cash'))])
    company_id = fields.Many2one('res.company',
                                 related='journal_id.company_id',
                                 string='Company',
                                 readonly=True,
                                 required=True)
    payment_method_id = fields.Many2one('account.payment.method',
                                        string='Payment Type',
                                        required=True)
    amount = fields.Monetary(string='Payment Amount', required=True)
    currency_id = fields.Many2one(
        'res.currency',
        string='Currency',
        required=True,
        default=lambda self: self.env.user.company_id.currency_id)
    payment_date = fields.Date(string='Payment Date',
                               default=fields.Date.context_today,
                               required=True)
    communication = fields.Char(string='Memo')
    hide_payment_method = fields.Boolean(
        compute='_compute_hide_payment_method',
        help=
        "Technical field used to hide the payment method if the selected journal has only one available which is 'manual'"
    )
    show_partner_bank_account = fields.Boolean(
        compute='_compute_show_partner_bank',
        help=
        'Technical field used to know whether the field `partner_bank_account_id` needs to be displayed or not in the payments form views'
    )

    @api.onchange('partner_id')
    def _onchange_partner_id(self):
        active_ids = self._context.get('active_ids', [])
        expense_sheet = self.env['hr.expense.sheet'].browse(active_ids)
        if expense_sheet.employee_id.id and expense_sheet.employee_id.sudo(
        ).bank_account_id.id:
            self.partner_bank_account_id = expense_sheet.employee_id.sudo(
            ).bank_account_id.id
        elif self.partner_id and len(self.partner_id.bank_ids) > 0:
            self.partner_bank_account_id = self.partner_id.bank_ids[0]
        else:
            self.partner_bank_account_id = False

    @api.one
    @api.constrains('amount')
    def _check_amount(self):
        if not self.amount > 0.0:
            raise ValidationError(
                _('The payment amount must be strictly positive.'))

    @api.depends('payment_method_id')
    def _compute_show_partner_bank(self):
        """ Computes if the destination bank account must be displayed in the payment form view. By default, it
        won't be displayed but some modules might change that, depending on the payment type."""
        for payment in self:
            payment.show_partner_bank_account = payment.payment_method_id.code in self.env[
                'account.payment']._get_method_codes_using_bank_account()

    @api.one
    @api.depends('journal_id')
    def _compute_hide_payment_method(self):
        if not self.journal_id:
            self.hide_payment_method = True
            return
        journal_payment_methods = self.journal_id.outbound_payment_method_ids
        self.hide_payment_method = len(
            journal_payment_methods
        ) == 1 and journal_payment_methods[0].code == 'manual'

    @api.onchange('journal_id')
    def _onchange_journal(self):
        if self.journal_id:
            # Set default payment method (we consider the first to be the default one)
            payment_methods = self.journal_id.outbound_payment_method_ids
            self.payment_method_id = payment_methods and payment_methods[
                0] or False
            # Set payment method domain (restrict to methods enabled for the journal and to selected payment type)
            return {
                'domain': {
                    'payment_method_id': [('payment_type', '=', 'outbound'),
                                          ('id', 'in', payment_methods.ids)]
                }
            }
        return {}

    def _get_payment_vals(self):
        """ Hook for extension """
        return {
            'partner_type': 'supplier',
            'payment_type': 'outbound',
            'partner_id': self.partner_id.id,
            'partner_bank_account_id': self.partner_bank_account_id.id,
            'journal_id': self.journal_id.id,
            'company_id': self.company_id.id,
            'payment_method_id': self.payment_method_id.id,
            'amount': self.amount,
            'currency_id': self.currency_id.id,
            'payment_date': self.payment_date,
            'communication': self.communication
        }

    @api.multi
    def expense_post_payment(self):
        self.ensure_one()
        context = dict(self._context or {})
        active_ids = context.get('active_ids', [])
        expense_sheet = self.env['hr.expense.sheet'].browse(active_ids)

        # Create payment and post it
        payment = self.env['account.payment'].create(self._get_payment_vals())
        payment.post()

        # Log the payment in the chatter
        body = (_(
            "A payment of %s %s with the reference <a href='/mail/view?%s'>%s</a> related to your expense %s has been made."
        ) % (payment.amount, payment.currency_id.symbol,
             url_encode({
                 'model': 'account.payment',
                 'res_id': payment.id
             }), payment.name, expense_sheet.name))
        expense_sheet.message_post(body=body)

        # Reconcile the payment and the expense, i.e. lookup on the payable account move lines
        account_move_lines_to_reconcile = self.env['account.move.line']
        for line in payment.move_line_ids + expense_sheet.account_move_id.line_ids:
            if line.account_id.internal_type == 'payable' and not line.reconciled:
                account_move_lines_to_reconcile |= line
        account_move_lines_to_reconcile.reconcile()

        return {'type': 'ir.actions.act_window_close'}
예제 #14
0
class SaleOrder(models.Model):
    _inherit = 'sale.order'

    amount_delivery = fields.Monetary(compute='_compute_amount_delivery',
                                      digits=0,
                                      string='Delivery Amount',
                                      help="The amount without tax.",
                                      store=True,
                                      track_visibility='always')
    has_delivery = fields.Boolean(compute='_compute_has_delivery',
                                  string='Has delivery',
                                  help="Has an order line set for delivery",
                                  store=True)

    @api.one
    def _compute_website_order_line(self):
        super(SaleOrder, self)._compute_website_order_line()
        self.website_order_line = self.website_order_line.filtered(
            lambda l: not l.is_delivery)

    @api.depends('order_line.price_unit', 'order_line.tax_id',
                 'order_line.discount', 'order_line.product_uom_qty')
    def _compute_amount_delivery(self):
        for order in self:
            if self.env.user.has_group(
                    'account.group_show_line_subtotals_tax_excluded'):
                order.amount_delivery = sum(
                    order.order_line.filtered('is_delivery').mapped(
                        'price_subtotal'))
            else:
                order.amount_delivery = sum(
                    order.order_line.filtered('is_delivery').mapped(
                        'price_total'))

    @api.depends('order_line.is_delivery')
    def _compute_has_delivery(self):
        for order in self:
            order.has_delivery = any(order.order_line.filtered('is_delivery'))

    def _check_carrier_quotation(self, force_carrier_id=None):
        self.ensure_one()
        DeliveryCarrier = self.env['delivery.carrier']

        if self.only_services:
            self.write({'carrier_id': None})
            self._remove_delivery_line()
            return True
        else:
            # attempt to use partner's preferred carrier
            if not force_carrier_id and self.partner_shipping_id.property_delivery_carrier_id:
                force_carrier_id = self.partner_shipping_id.property_delivery_carrier_id.id

            carrier = force_carrier_id and DeliveryCarrier.browse(
                force_carrier_id) or self.carrier_id
            available_carriers = self._get_delivery_methods()
            if carrier:
                if carrier not in available_carriers:
                    carrier = DeliveryCarrier
                else:
                    # set the forced carrier at the beginning of the list to be verfied first below
                    available_carriers -= carrier
                    available_carriers = carrier + available_carriers
            if force_carrier_id or not carrier or carrier not in available_carriers:
                for delivery in available_carriers:
                    verified_carrier = delivery._match_address(
                        self.partner_shipping_id)
                    if verified_carrier:
                        carrier = delivery
                        break
                self.write({'carrier_id': carrier.id})
            self._remove_delivery_line()
            if carrier:
                self.get_delivery_price()
                if self.delivery_rating_success:
                    self.set_delivery_line()

        return bool(carrier)

    def _get_delivery_methods(self):
        address = self.partner_shipping_id
        # searching on website_published will also search for available website (_search method on computed field)
        return self.env['delivery.carrier'].sudo().search([
            ('website_published', '=', True)
        ]).available_carriers(address)

    @api.multi
    def _cart_update(self,
                     product_id=None,
                     line_id=None,
                     add_qty=0,
                     set_qty=0,
                     **kwargs):
        """ Override to update carrier quotation if quantity changed """

        self._remove_delivery_line()

        # When you update a cart, it is not enouf to remove the "delivery cost" line
        # The carrier might also be invalid, eg: if you bought things that are too heavy
        # -> this may cause a bug if you go to the checkout screen, choose a carrier,
        #    then update your cart (the cart becomes uneditable)
        self.write({'carrier_id': False})

        values = super(SaleOrder,
                       self)._cart_update(product_id, line_id, add_qty,
                                          set_qty, **kwargs)

        return values
예제 #15
0
class AccountAnalyticAccount(models.Model):
    _name = 'account.analytic.account'
    _inherit = ['mail.thread']
    _description = 'Analytic Account'
    _order = 'code, name asc'

    @api.model
    def read_group(self,
                   domain,
                   fields,
                   groupby,
                   offset=0,
                   limit=None,
                   orderby=False,
                   lazy=True):
        """
            Override read_group to calculate the sum of the non-stored fields that depend on the user context
        """
        res = super(AccountAnalyticAccount, self).read_group(domain,
                                                             fields,
                                                             groupby,
                                                             offset=offset,
                                                             limit=limit,
                                                             orderby=orderby,
                                                             lazy=lazy)
        accounts = self.env['account.analytic.account']
        for line in res:
            if '__domain' in line:
                accounts = self.search(line['__domain'])
            if 'balance' in fields:
                line['balance'] = sum(accounts.mapped('balance'))
            if 'debit' in fields:
                line['debit'] = sum(accounts.mapped('debit'))
            if 'credit' in fields:
                line['credit'] = sum(accounts.mapped('credit'))
        return res

    @api.multi
    def _compute_debit_credit_balance(self):
        Curr = self.env['res.currency']
        analytic_line_obj = self.env['account.analytic.line']
        domain = [('account_id', 'in', self.ids)]
        if self._context.get('from_date', False):
            domain.append(('date', '>=', self._context['from_date']))
        if self._context.get('to_date', False):
            domain.append(('date', '<=', self._context['to_date']))
        if self._context.get('tag_ids'):
            tag_domain = expression.OR([[('tag_ids', 'in', [tag])]
                                        for tag in self._context['tag_ids']])
            domain = expression.AND([domain, tag_domain])
        if self._context.get('company_ids'):
            domain.append(('company_id', 'in', self._context['company_ids']))

        user_currency = self.env.user.company_id.currency_id
        credit_groups = analytic_line_obj.read_group(
            domain=domain + [('amount', '>=', 0.0)],
            fields=['account_id', 'currency_id', 'amount'],
            groupby=['account_id', 'currency_id'],
            lazy=False,
        )
        data_credit = defaultdict(float)
        for l in credit_groups:
            data_credit[l['account_id'][0]] += Curr.browse(
                l['currency_id'][0])._convert(l['amount'], user_currency,
                                              self.env.user.company_id,
                                              fields.Date.today())

        debit_groups = analytic_line_obj.read_group(
            domain=domain + [('amount', '<', 0.0)],
            fields=['account_id', 'currency_id', 'amount'],
            groupby=['account_id', 'currency_id'],
            lazy=False,
        )
        data_debit = defaultdict(float)
        for l in debit_groups:
            data_debit[l['account_id'][0]] += Curr.browse(
                l['currency_id'][0])._convert(l['amount'], user_currency,
                                              self.env.user.company_id,
                                              fields.Date.today())

        for account in self:
            account.debit = abs(data_debit.get(account.id, 0.0))
            account.credit = data_credit.get(account.id, 0.0)
            account.balance = account.credit - account.debit

    name = fields.Char(string='Analytic Account',
                       index=True,
                       required=True,
                       track_visibility='onchange')
    code = fields.Char(string='Reference',
                       index=True,
                       track_visibility='onchange')
    active = fields.Boolean(
        'Active',
        help=
        "If the active field is set to False, it will allow you to hide the account without removing it.",
        default=True)

    group_id = fields.Many2one('account.analytic.group', string='Group')

    line_ids = fields.One2many('account.analytic.line',
                               'account_id',
                               string="Analytic Lines")

    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 default=lambda self: self.env.user.company_id)

    # use auto_join to speed up name_search call
    partner_id = fields.Many2one('res.partner',
                                 string='Customer',
                                 auto_join=True,
                                 track_visibility='onchange')

    balance = fields.Monetary(compute='_compute_debit_credit_balance',
                              string='Balance')
    debit = fields.Monetary(compute='_compute_debit_credit_balance',
                            string='Debit')
    credit = fields.Monetary(compute='_compute_debit_credit_balance',
                             string='Credit')

    currency_id = fields.Many2one(related="company_id.currency_id",
                                  string="Currency",
                                  readonly=True)

    @api.multi
    def name_get(self):
        res = []
        for analytic in self:
            name = analytic.name
            if analytic.code:
                name = '[' + analytic.code + '] ' + name
            if analytic.partner_id.commercial_partner_id.name:
                name = name + ' - ' + analytic.partner_id.commercial_partner_id.name
            res.append((analytic.id, name))
        return res

    @api.model
    def _name_search(self,
                     name,
                     args=None,
                     operator='ilike',
                     limit=100,
                     name_get_uid=None):
        if operator not in ('ilike', 'like', '=', '=like', '=ilike'):
            return super(AccountAnalyticAccount,
                         self)._name_search(name,
                                            args,
                                            operator,
                                            limit,
                                            name_get_uid=name_get_uid)
        args = args or []
        if operator == 'ilike' and not (name or '').strip():
            domain = []
        else:
            # search by partner separately because auto_join field can break searches
            partner_domain = [('partner_id.name', operator, name)]
            ids_partner = self._search(expression.AND([partner_domain, args]),
                                       limit=limit,
                                       access_rights_uid=name_get_uid)
            domain = [
                '|', '|', ('code', operator, name), ('name', operator, name),
                ('id', 'in', ids_partner)
            ]
        analytic_account_ids = self._search(expression.AND([domain, args]),
                                            limit=limit,
                                            access_rights_uid=name_get_uid)
        return self.browse(analytic_account_ids).name_get()
예제 #16
0
파일: partner.py 프로젝트: metricsw/swerp
class ResPartner(models.Model):
    _name = 'res.partner'
    _inherit = 'res.partner'

    @api.multi
    def _credit_debit_get(self):
        tables, where_clause, where_params = self.env[
            'account.move.line'].with_context(
                state='posted',
                company_id=self.env.user.company_id.id)._query_get()
        where_params = [tuple(self.ids)] + where_params
        if where_clause:
            where_clause = 'AND ' + where_clause
        self._cr.execute(
            """SELECT account_move_line.partner_id, act.type, SUM(account_move_line.amount_residual)
                      FROM """ + tables + """
                      LEFT JOIN account_account a ON (account_move_line.account_id=a.id)
                      LEFT JOIN account_account_type act ON (a.user_type_id=act.id)
                      WHERE act.type IN ('receivable','payable')
                      AND account_move_line.partner_id IN %s
                      AND account_move_line.reconciled IS FALSE
                      """ + where_clause + """
                      GROUP BY account_move_line.partner_id, act.type
                      """, where_params)
        for pid, type, val in self._cr.fetchall():
            partner = self.browse(pid)
            if type == 'receivable':
                partner.credit = val
            elif type == 'payable':
                partner.debit = -val

    @api.multi
    def _asset_difference_search(self, account_type, operator, operand):
        if operator not in ('<', '=', '>', '>=', '<='):
            return []
        if type(operand) not in (float, int):
            return []
        sign = 1
        if account_type == 'payable':
            sign = -1
        res = self._cr.execute(
            '''
            SELECT partner.id
            FROM res_partner partner
            LEFT JOIN account_move_line aml ON aml.partner_id = partner.id
            JOIN account_move move ON move.id = aml.move_id
            RIGHT JOIN account_account acc ON aml.account_id = acc.id
            WHERE acc.internal_type = %s
              AND NOT acc.deprecated AND acc.company_id = %s
              AND move.state = 'posted'
            GROUP BY partner.id
            HAVING %s * COALESCE(SUM(aml.amount_residual), 0) ''' + operator +
            ''' %s''',
            (account_type, self.env.user.company_id.id, sign, operand))
        res = self._cr.fetchall()
        if not res:
            return [('id', '=', '0')]
        return [('id', 'in', [r[0] for r in res])]

    @api.model
    def _credit_search(self, operator, operand):
        return self._asset_difference_search('receivable', operator, operand)

    @api.model
    def _debit_search(self, operator, operand):
        return self._asset_difference_search('payable', operator, operand)

    @api.multi
    def _invoice_total(self):
        account_invoice_report = self.env['account.invoice.report']
        if not self.ids:
            return True

        user_currency_id = self.env.user.company_id.currency_id.id
        all_partners_and_children = {}
        all_partner_ids = []
        for partner in self:
            # price_total is in the company currency
            all_partners_and_children[partner] = self.with_context(
                active_test=False).search([('id', 'child_of', partner.id)]).ids
            all_partner_ids += all_partners_and_children[partner]

        # searching account.invoice.report via the ORM is comparatively expensive
        # (generates queries "id in []" forcing to build the full table).
        # In simple cases where all invoices are in the same currency than the user's company
        # access directly these elements

        # generate where clause to include multicompany rules
        where_query = account_invoice_report._where_calc([
            ('partner_id', 'in', all_partner_ids),
            ('state', 'not in', ['draft', 'cancel']),
            ('type', 'in', ('out_invoice', 'out_refund'))
        ])
        account_invoice_report._apply_ir_rules(where_query, 'read')
        from_clause, where_clause, where_clause_params = where_query.get_sql()

        # price_total is in the company currency
        query = """
                  SELECT SUM(price_total) as total, partner_id
                    FROM account_invoice_report account_invoice_report
                   WHERE %s
                   GROUP BY partner_id
                """ % where_clause
        self.env.cr.execute(query, where_clause_params)
        price_totals = self.env.cr.dictfetchall()
        for partner, child_ids in all_partners_and_children.items():
            partner.total_invoiced = sum(price['total']
                                         for price in price_totals
                                         if price['partner_id'] in child_ids)

    @api.multi
    def _compute_journal_item_count(self):
        AccountMoveLine = self.env['account.move.line']
        for partner in self:
            partner.journal_item_count = AccountMoveLine.search_count([
                ('partner_id', '=', partner.id)
            ])

    @api.multi
    def _compute_contracts_count(self):
        AccountAnalyticAccount = self.env['account.analytic.account']
        for partner in self:
            partner.contracts_count = AccountAnalyticAccount.search_count([
                ('partner_id', '=', partner.id)
            ])

    def get_followup_lines_domain(self,
                                  date,
                                  overdue_only=False,
                                  only_unblocked=False):
        domain = [
            ('reconciled', '=', False),
            ('account_id.deprecated', '=', False),
            ('account_id.internal_type', '=', 'receivable'),
            '|',
            ('debit', '!=', 0),
            ('credit', '!=', 0),
            ('company_id', '=', self.env.user.company_id.id),
            ('move_id.state', '=', 'posted'),
        ]
        if only_unblocked:
            domain += [('blocked', '=', False)]
        if self.ids:
            if 'exclude_given_ids' in self._context:
                domain += [('partner_id', 'not in', self.ids)]
            else:
                domain += [('partner_id', 'in', self.ids)]
        #adding the overdue lines
        overdue_domain = [
            '|', '&', ('date_maturity', '!=', False),
            ('date_maturity', '<', date), '&', ('date_maturity', '=', False),
            ('date', '<', date)
        ]
        if overdue_only:
            domain += overdue_domain
        return domain

    @api.one
    def _compute_has_unreconciled_entries(self):
        # Avoid useless work if has_unreconciled_entries is not relevant for this partner
        if not self.active or not self.is_company and self.parent_id:
            return
        self.env.cr.execute(
            """ SELECT 1 FROM(
                    SELECT
                        p.last_time_entries_checked AS last_time_entries_checked,
                        MAX(l.write_date) AS max_date
                    FROM
                        account_move_line l
                        RIGHT JOIN account_account a ON (a.id = l.account_id)
                        RIGHT JOIN res_partner p ON (l.partner_id = p.id)
                    WHERE
                        p.id = %s
                        AND EXISTS (
                            SELECT 1
                            FROM account_move_line l
                            WHERE l.account_id = a.id
                            AND l.partner_id = p.id
                            AND l.amount_residual > 0
                        )
                        AND EXISTS (
                            SELECT 1
                            FROM account_move_line l
                            WHERE l.account_id = a.id
                            AND l.partner_id = p.id
                            AND l.amount_residual < 0
                        )
                    GROUP BY p.last_time_entries_checked
                ) as s
                WHERE (last_time_entries_checked IS NULL OR max_date > last_time_entries_checked)
            """, (self.id, ))
        self.has_unreconciled_entries = self.env.cr.rowcount == 1

    @api.multi
    def mark_as_reconciled(self):
        self.env['account.partial.reconcile'].check_access_rights('write')
        return self.sudo().with_context(
            company_id=self.env.user.company_id.id).write({
                'last_time_entries_checked':
                time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
            })

    @api.one
    def _get_company_currency(self):
        if self.company_id:
            self.currency_id = self.sudo().company_id.currency_id
        else:
            self.currency_id = self.env.user.company_id.currency_id

    credit = fields.Monetary(compute='_credit_debit_get',
                             search=_credit_search,
                             string='Total Receivable',
                             help="Total amount this customer owes you.")
    debit = fields.Monetary(
        compute='_credit_debit_get',
        search=_debit_search,
        string='Total Payable',
        help="Total amount you have to pay to this vendor.")
    debit_limit = fields.Monetary('Payable Limit')
    total_invoiced = fields.Monetary(compute='_invoice_total',
                                     string="Total Invoiced",
                                     groups='account.group_account_invoice')
    currency_id = fields.Many2one(
        'res.currency',
        compute='_get_company_currency',
        readonly=True,
        string="Currency",
        help='Utility field to express amount currency')
    contracts_count = fields.Integer(compute='_compute_contracts_count',
                                     string="Contracts Count",
                                     type='integer')
    journal_item_count = fields.Integer(compute='_compute_journal_item_count',
                                        string="Journal Items",
                                        type="integer")
    property_account_payable_id = fields.Many2one(
        'account.account',
        company_dependent=True,
        string="Account Payable",
        oldname="property_account_payable",
        domain=
        "[('internal_type', '=', 'payable'), ('deprecated', '=', False)]",
        help=
        "This account will be used instead of the default one as the payable account for the current partner",
        required=True)
    property_account_receivable_id = fields.Many2one(
        'account.account',
        company_dependent=True,
        string="Account Receivable",
        oldname="property_account_receivable",
        domain=
        "[('internal_type', '=', 'receivable'), ('deprecated', '=', False)]",
        help=
        "This account will be used instead of the default one as the receivable account for the current partner",
        required=True)
    property_account_position_id = fields.Many2one(
        'account.fiscal.position',
        company_dependent=True,
        string="Fiscal Position",
        help=
        "The fiscal position determines the taxes/accounts used for this contact.",
        oldname="property_account_position")
    property_payment_term_id = fields.Many2one(
        'account.payment.term',
        company_dependent=True,
        string='Customer Payment Terms',
        help=
        "This payment term will be used instead of the default one for sales orders and customer invoices",
        oldname="property_payment_term")
    property_supplier_payment_term_id = fields.Many2one(
        'account.payment.term',
        company_dependent=True,
        string='Vendor Payment Terms',
        help=
        "This payment term will be used instead of the default one for purchase orders and vendor bills",
        oldname="property_supplier_payment_term")
    ref_company_ids = fields.One2many(
        'res.company',
        'partner_id',
        string='Companies that refers to partner',
        oldname="ref_companies")
    has_unreconciled_entries = fields.Boolean(
        compute='_compute_has_unreconciled_entries',
        help=
        "The partner has at least one unreconciled debit and credit since last time the invoices & payments matching was performed."
    )
    last_time_entries_checked = fields.Datetime(
        oldname='last_reconciliation_date',
        string='Latest Invoices & Payments Matching Date',
        readonly=True,
        copy=False,
        help=
        'Last time the invoices & payments matching was performed for this partner. '
        'It is set either if there\'s not at least an unreconciled debit and an unreconciled credit '
        'or if you click the "Done" button.')
    invoice_ids = fields.One2many('account.invoice',
                                  'partner_id',
                                  string='Invoices',
                                  readonly=True,
                                  copy=False)
    contract_ids = fields.One2many('account.analytic.account',
                                   'partner_id',
                                   string='Partner Contracts',
                                   readonly=True)
    bank_account_count = fields.Integer(compute='_compute_bank_count',
                                        string="Bank")
    trust = fields.Selection([('good', 'Good Debtor'),
                              ('normal', 'Normal Debtor'),
                              ('bad', 'Bad Debtor')],
                             string='Degree of trust you have in this debtor',
                             default='normal',
                             company_dependent=True)
    invoice_warn = fields.Selection(WARNING_MESSAGE,
                                    'Invoice',
                                    help=WARNING_HELP,
                                    default="no-message")
    invoice_warn_msg = fields.Text('Message for Invoice')

    @api.multi
    def _compute_bank_count(self):
        bank_data = self.env['res.partner.bank'].read_group(
            [('partner_id', 'in', self.ids)], ['partner_id'], ['partner_id'])
        mapped_data = dict([(bank['partner_id'][0], bank['partner_id_count'])
                            for bank in bank_data])
        for partner in self:
            partner.bank_account_count = mapped_data.get(partner.id, 0)

    def _find_accounting_partner(self, partner):
        ''' Find the partner for which the accounting entries will be created '''
        return partner.commercial_partner_id

    @api.model
    def _commercial_fields(self):
        return super(ResPartner, self)._commercial_fields() + \
            ['debit_limit', 'property_account_payable_id', 'property_account_receivable_id', 'property_account_position_id',
             'property_payment_term_id', 'property_supplier_payment_term_id', 'last_time_entries_checked']

    @api.multi
    def action_view_partner_invoices(self):
        self.ensure_one()
        action = self.env.ref(
            'account.action_invoice_refund_out_tree').read()[0]
        action['domain'] = literal_eval(action['domain'])
        action['domain'].append(('partner_id', 'child_of', self.id))
        return action

    @api.onchange('company_id')
    def _onchange_company_id(self):
        company = self.env['res.company']
        if self.company_id:
            company = self.company_id
        else:
            company = self.env.user.company_id
        return {
            'domain': {
                'property_account_position_id':
                [('company_id', 'in', [company.id, False])]
            }
        }

    def can_edit_vat(self):
        ''' Can't edit `vat` if there is (non draft) issued invoices. '''
        can_edit_vat = super(ResPartner, self).can_edit_vat()
        if not can_edit_vat:
            return can_edit_vat
        Invoice = self.env['account.invoice']
        has_invoice = Invoice.search(
            [('type', 'in', ['out_invoice', 'out_refund']),
             ('partner_id', 'child_of', self.commercial_partner_id.id),
             ('state', 'not in', ['draft', 'cancel'])],
            limit=1)
        return can_edit_vat and not (bool(has_invoice))
예제 #17
0
class ResConfigSettings(models.TransientModel):
    _inherit = 'res.config.settings'

    lock_confirmed_po = fields.Boolean(
        "Lock Confirmed Orders",
        default=lambda self: self.env.user.company_id.po_lock == 'lock')
    po_lock = fields.Selection(related='company_id.po_lock',
                               string="Purchase Order Modification *",
                               readonly=False)
    po_order_approval = fields.Boolean(
        "Purchase Order Approval",
        default=lambda self: self.env.user.company_id.po_double_validation ==
        'two_step')
    po_double_validation = fields.Selection(
        related='company_id.po_double_validation',
        string="Levels of Approvals *",
        readonly=False)
    po_double_validation_amount = fields.Monetary(
        related='company_id.po_double_validation_amount',
        string="Minimum Amount",
        currency_field='company_currency_id',
        readonly=False)
    company_currency_id = fields.Many2one(
        'res.currency',
        related='company_id.currency_id',
        string="Company Currency",
        readonly=True,
        help='Utility field to express amount currency')
    default_purchase_method = fields.Selection(
        [
            ('purchase', 'Ordered quantities'),
            ('receive', 'Delivered quantities'),
        ],
        string="Bill Control",
        default_model="product.template",
        help="This default value is applied to any new product created. "
        "This can be changed in the product detail form.",
        default="receive")
    group_warning_purchase = fields.Boolean(
        "Purchase Warnings", implied_group='purchase.group_warning_purchase')
    group_manage_vendor_price = fields.Boolean(
        "Vendor Pricelists",
        implied_group="purchase.group_manage_vendor_price")
    module_account_3way_match = fields.Boolean(
        "3-way matching: purchases, receptions and bills")
    module_purchase_requisition = fields.Boolean("Purchase Agreements")
    po_lead = fields.Float(related='company_id.po_lead', readonly=False)
    use_po_lead = fields.Boolean(
        string="Security Lead Time for Purchase",
        oldname='default_new_po_lead',
        config_parameter='purchase.use_po_lead',
        help=
        "Margin of error for vendor lead times. When the system generates Purchase Orders for reordering products,they will be scheduled that many days earlier to cope with unexpected vendor delays."
    )

    @api.onchange('use_po_lead')
    def _onchange_use_po_lead(self):
        if not self.use_po_lead:
            self.po_lead = 0.0

    def set_values(self):
        super(ResConfigSettings, self).set_values()
        self.po_lock = 'lock' if self.lock_confirmed_po else 'edit'
        self.po_double_validation = 'two_step' if self.po_order_approval else 'one_step'
예제 #18
0
class ProjectCreateInvoice(models.TransientModel):
    _name = 'project.create.invoice'
    _description = "Create Invoice from project"

    @api.model
    def default_get(self, fields):
        result = super(ProjectCreateInvoice, self).default_get(fields)

        active_model = self._context.get('active_model')
        if active_model != 'project.project':
            raise UserError(
                _('You can only apply this action from a project.'))

        active_id = self._context.get('active_id')
        if 'project_id' in fields and active_id:
            result['project_id'] = active_id
        return result

    project_id = fields.Many2one('project.project',
                                 "Project",
                                 help="Project to make billable",
                                 required=True)
    sale_order_id = fields.Many2one('sale.order',
                                    string="Choose the Sales Order to invoice",
                                    required=True)
    amount_to_invoice = fields.Monetary(
        "Amount to invoice",
        compute='_compute_amount_to_invoice',
        currency_field='currency_id',
        help=
        "Total amount to invoice on the sales order, including all items (services, storables, expenses, ...)"
    )
    currency_id = fields.Many2one(related='sale_order_id.currency_id',
                                  readonly=True)

    @api.onchange('project_id')
    def _onchange_project_id(self):
        sale_orders = self.project_id.tasks.mapped(
            'sale_line_id.order_id').filtered(
                lambda so: so.invoice_status == 'to invoice')
        return {
            'domain': {
                'sale_order_id': [('id', 'in', sale_orders.ids)]
            },
        }

    @api.multi
    @api.depends('sale_order_id')
    def _compute_amount_to_invoice(self):
        for wizard in self:
            amount_untaxed = 0.0
            amount_tax = 0.0
            for line in wizard.sale_order_id.order_line.filtered(
                    lambda sol: sol.invoice_status == 'to invoice'):
                amount_untaxed += line.price_reduce * line.qty_to_invoice
                amount_tax += line.price_tax
            wizard.amount_to_invoice = amount_untaxed + amount_tax

    @api.multi
    def action_create_invoice(self):
        if not self.sale_order_id and self.sale_order_id.invoice_status != 'to invoice':
            raise UserError(
                _("The selected Sales Order should contain something to invoice."
                  ))
        action = self.env.ref(
            'sale.action_view_sale_advance_payment_inv').read()[0]
        action['context'] = {'active_ids': self.sale_order_id.ids}
        return action
예제 #19
0
class AccountVoucher(models.Model):
    _name = 'account.voucher'
    _description = 'Accounting Voucher'
    _inherit = ['mail.thread']
    _order = "date desc, id desc"

    @api.model
    def _default_journal(self):
        voucher_type = self._context.get('voucher_type', 'sale')
        company_id = self._context.get('company_id',
                                       self.env.user.company_id.id)
        domain = [
            ('type', '=', voucher_type),
            ('company_id', '=', company_id),
        ]
        return self.env['account.journal'].search(domain, limit=1)

    @api.model
    def _default_payment_journal(self):
        company_id = self._context.get('company_id',
                                       self.env.user.company_id.id)
        domain = [
            ('type', 'in', ('bank', 'cash')),
            ('company_id', '=', company_id),
        ]
        return self.env['account.journal'].search(domain, limit=1)

    voucher_type = fields.Selection([('sale', 'Sale'),
                                     ('purchase', 'Purchase')],
                                    string='Type',
                                    readonly=True,
                                    states={'draft': [('readonly', False)]},
                                    oldname="type")
    name = fields.Char('Payment Memo',
                       readonly=True,
                       states={'draft': [('readonly', False)]},
                       default='',
                       copy=False)
    date = fields.Date("Bill Date",
                       readonly=True,
                       index=True,
                       states={'draft': [('readonly', False)]},
                       copy=False,
                       default=fields.Date.context_today)
    account_date = fields.Date("Accounting Date",
                               readonly=True,
                               index=True,
                               states={'draft': [('readonly', False)]},
                               help="Effective date for accounting entries",
                               copy=False,
                               default=fields.Date.context_today)
    journal_id = fields.Many2one('account.journal',
                                 'Journal',
                                 required=True,
                                 readonly=True,
                                 states={'draft': [('readonly', False)]},
                                 default=_default_journal)
    payment_journal_id = fields.Many2one(
        'account.journal',
        string='Payment Method',
        readonly=True,
        states={'draft': [('readonly', False)]},
        domain="[('type', 'in', ['cash', 'bank'])]",
        default=_default_payment_journal)
    account_id = fields.Many2one(
        'account.account',
        'Account',
        required=True,
        readonly=True,
        states={'draft': [('readonly', False)]},
        domain=
        "[('deprecated', '=', False), ('internal_type','=', (voucher_type == 'purchase' and 'payable' or 'receivable'))]"
    )
    line_ids = fields.One2many('account.voucher.line',
                               'voucher_id',
                               'Voucher Lines',
                               readonly=True,
                               copy=True,
                               states={'draft': [('readonly', False)]})
    narration = fields.Text('Notes',
                            readonly=True,
                            states={'draft': [('readonly', False)]})
    currency_id = fields.Many2one('res.currency',
                                  compute='_get_journal_currency',
                                  string='Currency',
                                  readonly=True,
                                  store=True,
                                  default=lambda self: self._get_currency())
    company_id = fields.Many2one('res.company',
                                 'Company',
                                 store=True,
                                 readonly=True,
                                 states={'draft': [('readonly', False)]},
                                 related='journal_id.company_id',
                                 default=lambda self: self._get_company())
    state = fields.Selection(
        [('draft', 'Draft'), ('cancel', 'Cancelled'),
         ('proforma', 'Pro-forma'), ('posted', 'Posted')],
        'Status',
        readonly=True,
        track_visibility='onchange',
        copy=False,
        default='draft',
        help=
        " * The 'Draft' status is used when a user is encoding a new and unconfirmed Voucher.\n"
        " * The 'Pro-forma' status is used when the voucher does not have a voucher number.\n"
        " * The 'Posted' status is used when user create voucher,a voucher number is generated and voucher entries are created in account.\n"
        " * The 'Cancelled' status is used when user cancel voucher.")
    reference = fields.Char('Bill Reference',
                            readonly=True,
                            states={'draft': [('readonly', False)]},
                            help="The partner reference of this document.",
                            copy=False)
    amount = fields.Monetary(string='Total',
                             store=True,
                             readonly=True,
                             compute='_compute_total')
    tax_amount = fields.Monetary(readonly=True,
                                 store=True,
                                 compute='_compute_total')
    tax_correction = fields.Monetary(
        readonly=True,
        states={'draft': [('readonly', False)]},
        help=
        'In case we have a rounding problem in the tax, use this field to correct it'
    )
    number = fields.Char(readonly=True, copy=False)
    move_id = fields.Many2one('account.move', 'Journal Entry', copy=False)
    partner_id = fields.Many2one('res.partner',
                                 'Partner',
                                 change_default=1,
                                 readonly=True,
                                 states={'draft': [('readonly', False)]})
    paid = fields.Boolean(compute='_check_paid',
                          help="The Voucher has been totally paid.")
    pay_now = fields.Selection([
        ('pay_now', 'Pay Directly'),
        ('pay_later', 'Pay Later'),
    ],
                               'Payment',
                               index=True,
                               readonly=True,
                               states={'draft': [('readonly', False)]},
                               default='pay_later')
    date_due = fields.Date('Due Date',
                           readonly=True,
                           index=True,
                           states={'draft': [('readonly', False)]})

    @api.one
    @api.depends('move_id.line_ids.reconciled',
                 'move_id.line_ids.account_id.internal_type')
    def _check_paid(self):
        self.paid = any([((line.account_id.internal_type, 'in',
                           ('receivable', 'payable')) and line.reconciled)
                         for line in self.move_id.line_ids])

    @api.model
    def _get_currency(self):
        journal = self.env['account.journal'].browse(
            self.env.context.get('default_journal_id', False))
        if journal.currency_id:
            return journal.currency_id.id
        return self.env.user.company_id.currency_id.id

    @api.model
    def _get_company(self):
        return self._context.get('company_id', self.env.user.company_id.id)

    @api.constrains('company_id', 'currency_id')
    def _check_company_id(self):
        for voucher in self:
            if not voucher.company_id:
                raise ValidationError(_("Missing Company"))
            if not voucher.currency_id:
                raise ValidationError(_("Missing Currency"))

    @api.multi
    @api.depends('name', 'number')
    def name_get(self):
        return [(r.id, (r.number or _('Voucher'))) for r in self]

    @api.one
    @api.depends('journal_id', 'company_id')
    def _get_journal_currency(self):
        self.currency_id = self.journal_id.currency_id.id or self.company_id.currency_id.id

    @api.multi
    @api.depends('tax_correction', 'line_ids.price_subtotal')
    def _compute_total(self):
        tax_calculation_rounding_method = self.env.user.company_id.tax_calculation_rounding_method
        for voucher in self:
            total = 0
            tax_amount = 0
            tax_lines_vals_merged = {}
            for line in voucher.line_ids:
                tax_info = line.tax_ids.compute_all(line.price_unit,
                                                    voucher.currency_id,
                                                    line.quantity,
                                                    line.product_id,
                                                    voucher.partner_id)
                if tax_calculation_rounding_method == 'round_globally':
                    total += tax_info.get('total_excluded', 0.0)
                    for t in tax_info.get('taxes', False):
                        key = (
                            t['id'],
                            t['account_id'],
                        )
                        if key not in tax_lines_vals_merged:
                            tax_lines_vals_merged[key] = t.get('amount', 0.0)
                        else:
                            tax_lines_vals_merged[key] += t.get('amount', 0.0)
                else:
                    total += tax_info.get('total_included', 0.0)
                    tax_amount += sum([
                        t.get('amount', 0.0)
                        for t in tax_info.get('taxes', False)
                    ])
            if tax_calculation_rounding_method == 'round_globally':
                tax_amount = sum([
                    voucher.currency_id.round(t)
                    for t in tax_lines_vals_merged.values()
                ])
                voucher.amount = total + tax_amount + voucher.tax_correction
            else:
                voucher.amount = total + voucher.tax_correction
            voucher.tax_amount = tax_amount

    @api.onchange('date')
    def onchange_date(self):
        self.account_date = self.date

    @api.onchange('partner_id', 'pay_now')
    def onchange_partner_id(self):
        pay_journal_domain = [('type', 'in', ['cash', 'bank'])]
        if self.partner_id:
            self.account_id = self.partner_id.property_account_receivable_id \
                if self.voucher_type == 'sale' else self.partner_id.property_account_payable_id
        else:
            if self.voucher_type == 'purchase':
                pay_journal_domain.append(
                    ('outbound_payment_method_ids', '!=', False))
            else:
                pay_journal_domain.append(
                    ('inbound_payment_method_ids', '!=', False))
        return {'domain': {'payment_journal_id': pay_journal_domain}}

    @api.multi
    def proforma_voucher(self):
        self.action_move_line_create()

    @api.multi
    def action_cancel_draft(self):
        self.write({'state': 'draft'})

    @api.multi
    def cancel_voucher(self):
        for voucher in self:
            voucher.move_id.button_cancel()
            voucher.move_id.unlink()
        self.write({'state': 'cancel', 'move_id': False})

    @api.multi
    def unlink(self):
        for voucher in self:
            if voucher.state not in ('draft', 'cancel'):
                raise UserError(
                    _('Cannot delete voucher(s) which are already opened or paid.'
                      ))
        return super(AccountVoucher, self).unlink()

    @api.multi
    def first_move_line_get(self, move_id, company_currency, current_currency):
        debit = credit = 0.0
        if self.voucher_type == 'purchase':
            credit = self._convert(self.amount)
        elif self.voucher_type == 'sale':
            debit = self._convert(self.amount)
        if debit < 0.0: debit = 0.0
        if credit < 0.0: credit = 0.0
        sign = debit - credit < 0 and -1 or 1
        #set the first line of the voucher
        move_line = {
            'name':
            self.name or '/',
            'debit':
            debit,
            'credit':
            credit,
            'account_id':
            self.account_id.id,
            'move_id':
            move_id,
            'journal_id':
            self.journal_id.id,
            'partner_id':
            self.partner_id.commercial_partner_id.id,
            'currency_id':
            company_currency != current_currency and current_currency or False,
            'amount_currency': (
                sign * abs(self.amount)  # amount < 0 for refunds
                if company_currency != current_currency else 0.0),
            'date':
            self.account_date,
            'date_maturity':
            self.date_due,
        }
        return move_line

    @api.multi
    def account_move_get(self):
        if self.number:
            name = self.number
        elif self.journal_id.sequence_id:
            if not self.journal_id.sequence_id.active:
                raise UserError(
                    _('Please activate the sequence of selected journal !'))
            name = self.journal_id.sequence_id.with_context(
                ir_sequence_date=self.date).next_by_id()
        else:
            raise UserError(_('Please define a sequence on the journal.'))

        move = {
            'name': name,
            'journal_id': self.journal_id.id,
            'narration': self.narration,
            'date': self.account_date,
            'ref': self.reference,
        }
        return move

    @api.multi
    def _convert(self, amount):
        '''
        This function convert the amount given in company currency. It takes either the rate in the voucher (if the
        payment_rate_currency_id is relevant) either the rate encoded in the system.
        :param amount: float. The amount to convert
        :param voucher: id of the voucher on which we want the conversion
        :param context: to context to use for the conversion. It may contain the key 'date' set to the voucher date
            field in order to select the good rate to use.
        :return: the amount in the currency of the voucher's company
        :rtype: float
        '''
        for voucher in self:
            return voucher.currency_id._convert(amount,
                                                voucher.company_id.currency_id,
                                                voucher.company_id,
                                                voucher.account_date)

    @api.multi
    def voucher_pay_now_payment_create(self):
        if self.voucher_type == 'sale':
            payment_methods = self.journal_id.inbound_payment_method_ids
            payment_type = 'inbound'
            partner_type = 'customer'
            sequence_code = 'account.payment.customer.invoice'
        else:
            payment_methods = self.journal_id.outbound_payment_method_ids
            payment_type = 'outbound'
            partner_type = 'supplier'
            sequence_code = 'account.payment.supplier.invoice'
        return {
            'payment_type': payment_type,
            'payment_method_id': payment_methods and payment_methods[0].id
            or False,
            'partner_type': partner_type,
            'partner_id': self.partner_id.commercial_partner_id.id,
            'amount': self.amount,
            'currency_id': self.currency_id.id,
            'payment_date': self.date,
            'journal_id': self.payment_journal_id.id,
            'communication': self.name,
        }

    @api.multi
    def voucher_move_line_create(self, line_total, move_id, company_currency,
                                 current_currency):
        '''
        Create one account move line, on the given account move, per voucher line where amount is not 0.0.
        It returns Tuple with tot_line what is total of difference between debit and credit and
        a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists).

        :param voucher_id: Voucher id what we are working with
        :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines.
        :param move_id: Account move wher those lines will be joined.
        :param company_currency: id of currency of the company to which the voucher belong
        :param current_currency: id of currency of the voucher
        :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method)
        :rtype: tuple(float, list of int)
        '''
        tax_calculation_rounding_method = self.env.user.company_id.tax_calculation_rounding_method
        tax_lines_vals = []
        for line in self.line_ids:
            #create one move line per voucher line where amount is not 0.0
            if not line.price_subtotal:
                continue
            line_subtotal = line.price_subtotal
            if self.voucher_type == 'sale':
                line_subtotal = -1 * line.price_subtotal
            # convert the amount set on the voucher line into the currency of the voucher's company
            amount = self._convert(line.price_unit * line.quantity)
            move_line = {
                'journal_id':
                self.journal_id.id,
                'name':
                line.name or '/',
                'account_id':
                line.account_id.id,
                'move_id':
                move_id,
                'quantity':
                line.quantity,
                'product_id':
                line.product_id.id,
                'partner_id':
                self.partner_id.commercial_partner_id.id,
                'analytic_account_id':
                line.account_analytic_id and line.account_analytic_id.id
                or False,
                'analytic_tag_ids': [(6, 0, line.analytic_tag_ids.ids)],
                'credit':
                abs(amount) if self.voucher_type == 'sale' else 0.0,
                'debit':
                abs(amount) if self.voucher_type == 'purchase' else 0.0,
                'date':
                self.account_date,
                'tax_ids': [(4, t.id) for t in line.tax_ids],
                'amount_currency':
                line_subtotal if current_currency != company_currency else 0.0,
                'currency_id':
                company_currency != current_currency and current_currency
                or False,
                'payment_id':
                self._context.get('payment_id'),
            }
            # Create one line per tax and fix debit-credit for the move line if there are tax included
            if (line.tax_ids
                    and tax_calculation_rounding_method == 'round_per_line'):
                tax_group = line.tax_ids.compute_all(
                    self._convert(line.price_unit),
                    self.company_id.currency_id, line.quantity,
                    line.product_id, self.partner_id)
                if move_line['debit']:
                    move_line['debit'] = tax_group['total_excluded']
                if move_line['credit']:
                    move_line['credit'] = tax_group['total_excluded']
                Currency = self.env['res.currency']
                company_cur = Currency.browse(company_currency)
                current_cur = Currency.browse(current_currency)
                for tax_vals in tax_group['taxes']:
                    if tax_vals['amount']:
                        tax = self.env['account.tax'].browse([tax_vals['id']])
                        account_id = (amount > 0 and tax_vals['account_id']
                                      or tax_vals['refund_account_id'])
                        if not account_id: account_id = line.account_id.id
                        temp = {
                            'account_id':
                            account_id,
                            'name':
                            line.name + ' ' + tax_vals['name'],
                            'tax_line_id':
                            tax_vals['id'],
                            'move_id':
                            move_id,
                            'date':
                            self.account_date,
                            'partner_id':
                            self.partner_id.id,
                            'debit':
                            self.voucher_type != 'sale' and tax_vals['amount']
                            or 0.0,
                            'credit':
                            self.voucher_type == 'sale' and tax_vals['amount']
                            or 0.0,
                            'analytic_account_id':
                            line.account_analytic_id
                            and line.account_analytic_id.id or False,
                        }
                        if company_currency != current_currency:
                            ctx = {}
                            sign = temp['credit'] and -1 or 1
                            amount_currency = company_cur._convert(
                                tax_vals['amount'],
                                current_cur,
                                line.company_id,
                                self.account_date or fields.Date.today(),
                                round=True)
                            if self.account_date:
                                ctx['date'] = self.account_date
                            temp['currency_id'] = current_currency
                            temp['amount_currency'] = sign * abs(
                                amount_currency)
                        self.env['account.move.line'].create(temp)

            # When global rounding is activated, we must wait until all tax lines are computed to
            # merge them.
            if tax_calculation_rounding_method == 'round_globally':
                # _apply_taxes modifies the dict move_line in place to account for included/excluded taxes
                tax_lines_vals += self.env['account.move.line'].with_context(
                    round=False)._apply_taxes(
                        move_line,
                        move_line.get('debit', 0.0) -
                        move_line.get('credit', 0.0))
                # rounding False means the move_line's amount are not rounded
                currency = self.env['res.currency'].browse(company_currency)
                move_line['debit'] = currency.round(move_line['debit'])
                move_line['credit'] = currency.round(move_line['credit'])
            self.env['account.move.line'].create(move_line)

        # When round globally is set, we merge the tax lines
        if tax_calculation_rounding_method == 'round_globally':
            tax_lines_vals_merged = {}
            for tax_line_vals in tax_lines_vals:
                key = (
                    tax_line_vals['tax_line_id'],
                    tax_line_vals['account_id'],
                    tax_line_vals['analytic_account_id'],
                )
                if key not in tax_lines_vals_merged:
                    tax_lines_vals_merged[key] = tax_line_vals
                else:
                    tax_lines_vals_merged[key]['debit'] += tax_line_vals[
                        'debit']
                    tax_lines_vals_merged[key]['credit'] += tax_line_vals[
                        'credit']
            currency = self.env['res.currency'].browse(company_currency)
            for vals in tax_lines_vals_merged.values():
                vals['debit'] = currency.round(vals['debit'])
                vals['credit'] = currency.round(vals['credit'])
                self.env['account.move.line'].create(vals)
        return line_total

    @api.multi
    def action_move_line_create(self):
        '''
        Confirm the vouchers given in ids and create the journal entries for each of them
        '''
        for voucher in self:
            local_context = dict(
                self._context, force_company=voucher.journal_id.company_id.id)
            if voucher.move_id:
                continue
            company_currency = voucher.journal_id.company_id.currency_id.id
            current_currency = voucher.currency_id.id or company_currency
            # we select the context to use accordingly if it's a multicurrency case or not
            # But for the operations made by _convert, we always need to give the date in the context
            ctx = local_context.copy()
            ctx['date'] = voucher.account_date
            ctx['check_move_validity'] = False
            # Create the account move record.
            move = self.env['account.move'].create(voucher.account_move_get())
            # Get the name of the account_move just created
            # Create the first line of the voucher
            move_line = self.env['account.move.line'].with_context(ctx).create(
                voucher.with_context(ctx).first_move_line_get(
                    move.id, company_currency, current_currency))
            line_total = move_line.debit - move_line.credit
            if voucher.voucher_type == 'sale':
                line_total = line_total - voucher._convert(voucher.tax_amount)
            elif voucher.voucher_type == 'purchase':
                line_total = line_total + voucher._convert(voucher.tax_amount)
            # Create one move line per voucher line where amount is not 0.0
            line_total = voucher.with_context(ctx).voucher_move_line_create(
                line_total, move.id, company_currency, current_currency)

            # Create a payment to allow the reconciliation when pay_now = 'pay_now'.
            if voucher.pay_now == 'pay_now':
                payment_id = (self.env['account.payment'].with_context(
                    force_counterpart_account=voucher.account_id.id).create(
                        voucher.voucher_pay_now_payment_create()))
                payment_id.post()

                # Reconcile the receipt with the payment
                lines_to_reconcile = (
                    payment_id.move_line_ids + move.line_ids
                ).filtered(lambda l: l.account_id == voucher.account_id)
                lines_to_reconcile.reconcile()

            # Add tax correction to move line if any tax correction specified
            if voucher.tax_correction != 0.0:
                tax_move_line = self.env['account.move.line'].search(
                    [('move_id', '=', move.id), ('tax_line_id', '!=', False)],
                    limit=1)
                if len(tax_move_line):
                    tax_move_line.write({
                        'debit':
                        tax_move_line.debit + voucher.tax_correction
                        if tax_move_line.debit > 0 else 0,
                        'credit':
                        tax_move_line.credit + voucher.tax_correction
                        if tax_move_line.credit > 0 else 0
                    })

            # We post the voucher.
            voucher.write({
                'move_id': move.id,
                'state': 'posted',
                'number': move.name
            })
            move.post()
        return True

    @api.multi
    def _track_subtype(self, init_values):
        if 'state' in init_values:
            return 'account_voucher.mt_voucher_state_change'
        return super(AccountVoucher, self)._track_subtype(init_values)
예제 #20
0
class AccountVoucherLine(models.Model):
    _name = 'account.voucher.line'
    _description = 'Accounting Voucher Line'

    name = fields.Text(string='Description', required=True)
    sequence = fields.Integer(
        default=10,
        help="Gives the sequence of this line when displaying the voucher.")
    voucher_id = fields.Many2one('account.voucher',
                                 'Voucher',
                                 required=1,
                                 ondelete='cascade')
    product_id = fields.Many2one('product.product',
                                 string='Product',
                                 ondelete='set null',
                                 index=True)
    account_id = fields.Many2one(
        'account.account',
        string='Account',
        required=True,
        domain=[('deprecated', '=', False)],
        help="The income or expense account related to the selected product.")
    price_unit = fields.Float(string='Unit Price',
                              required=True,
                              digits=dp.get_precision('Product Price'),
                              oldname='amount')
    price_subtotal = fields.Monetary(string='Amount',
                                     store=True,
                                     readonly=True,
                                     compute='_compute_subtotal')
    quantity = fields.Float(digits=dp.get_precision('Product Unit of Measure'),
                            required=True,
                            default=1)
    account_analytic_id = fields.Many2one('account.analytic.account',
                                          'Analytic Account')
    analytic_tag_ids = fields.Many2many('account.analytic.tag',
                                        string='Analytic Tags')
    company_id = fields.Many2one('res.company',
                                 related='voucher_id.company_id',
                                 string='Company',
                                 store=True,
                                 readonly=True)
    tax_ids = fields.Many2many('account.tax',
                               string='Tax',
                               help="Only for tax excluded from price")
    currency_id = fields.Many2one('res.currency',
                                  related='voucher_id.currency_id',
                                  readonly=False)

    @api.one
    @api.depends('price_unit', 'tax_ids', 'quantity', 'product_id',
                 'voucher_id.currency_id')
    def _compute_subtotal(self):
        self.price_subtotal = self.quantity * self.price_unit
        if self.tax_ids:
            taxes = self.tax_ids.compute_all(
                self.price_unit,
                self.voucher_id.currency_id,
                self.quantity,
                product=self.product_id,
                partner=self.voucher_id.partner_id)
            self.price_subtotal = taxes['total_excluded']

    @api.onchange('product_id', 'voucher_id', 'price_unit', 'company_id')
    def _onchange_line_details(self):
        if not self.voucher_id or not self.product_id or not self.voucher_id.partner_id:
            return
        onchange_res = self.product_id_change(self.product_id.id,
                                              self.voucher_id.partner_id.id,
                                              self.price_unit,
                                              self.company_id.id,
                                              self.voucher_id.currency_id.id,
                                              self.voucher_id.voucher_type)
        for fname, fvalue in onchange_res['value'].items():
            setattr(self, fname, fvalue)

    def _get_account(self, product, fpos, type):
        accounts = product.product_tmpl_id.get_product_accounts(fpos)
        if type == 'sale':
            return accounts['income']
        return accounts['expense']

    @api.multi
    def product_id_change(self,
                          product_id,
                          partner_id=False,
                          price_unit=False,
                          company_id=None,
                          currency_id=None,
                          type=None):
        # TDE note: mix of old and new onchange badly written in 9, multi but does not use record set
        context = self._context
        company_id = company_id if company_id is not None else context.get(
            'company_id', False)
        company = self.env['res.company'].browse(company_id)
        currency = self.env['res.currency'].browse(currency_id)
        if not partner_id:
            raise UserError(_("You must first select a partner."))
        part = self.env['res.partner'].browse(partner_id)
        if part.lang:
            self = self.with_context(lang=part.lang)

        product = self.env['product.product'].browse(product_id)
        fpos = part.property_account_position_id
        account = self._get_account(product, fpos, type)
        values = {
            'name': product.partner_ref,
            'account_id': account.id,
        }

        if type == 'purchase':
            values['price_unit'] = price_unit or product.standard_price
            taxes = product.supplier_taxes_id or account.tax_ids
            if product.description_purchase:
                values['name'] += '\n' + product.description_purchase
        else:
            values['price_unit'] = price_unit or product.lst_price
            taxes = product.taxes_id or account.tax_ids
            if product.description_sale:
                values['name'] += '\n' + product.description_sale

        values['tax_ids'] = taxes.ids

        if company and currency:
            if company.currency_id != currency:
                if type == 'purchase':
                    values['price_unit'] = price_unit or product.standard_price
                values['price_unit'] = values['price_unit'] * currency.rate

        return {'value': values, 'domain': {}}
예제 #21
0
파일: hr.py 프로젝트: metricsw/swerp
class HrEmployee(models.Model):
    _inherit = 'hr.employee'

    timesheet_cost = fields.Monetary('Timesheet Cost', currency_field='currency_id', default=0.0)
    currency_id = fields.Many2one('res.currency', related='company_id.currency_id', readonly=True)
예제 #22
0
class AccountBankStatement(models.Model):
    @api.one
    @api.depends('line_ids', 'balance_start', 'line_ids.amount',
                 'balance_end_real')
    def _end_balance(self):
        self.total_entry_encoding = sum(
            [line.amount for line in self.line_ids])
        self.balance_end = self.balance_start + self.total_entry_encoding
        self.difference = self.balance_end_real - self.balance_end

    @api.multi
    def _is_difference_zero(self):
        for bank_stmt in self:
            bank_stmt.is_difference_zero = float_is_zero(
                bank_stmt.difference,
                precision_digits=bank_stmt.currency_id.decimal_places)

    @api.one
    @api.depends('journal_id')
    def _compute_currency(self):
        self.currency_id = self.journal_id.currency_id or self.company_id.currency_id

    @api.one
    @api.depends('line_ids.journal_entry_ids')
    def _check_lines_reconciled(self):
        self.all_lines_reconciled = all([
            line.journal_entry_ids.ids or line.account_id.id
            for line in self.line_ids
            if not self.currency_id.is_zero(line.amount)
        ])

    @api.depends('move_line_ids')
    def _get_move_line_count(self):
        for payment in self:
            payment.move_line_count = len(payment.move_line_ids)

    @api.model
    def _default_journal(self):
        journal_type = self.env.context.get('journal_type', False)
        company_id = self.env['res.company']._company_default_get(
            'account.bank.statement').id
        if journal_type:
            journals = self.env['account.journal'].search([
                ('type', '=', journal_type), ('company_id', '=', company_id)
            ])
            if journals:
                return journals[0]
        return self.env['account.journal']

    @api.multi
    def _get_opening_balance(self, journal_id):
        last_bnk_stmt = self.search([('journal_id', '=', journal_id)], limit=1)
        if last_bnk_stmt:
            return last_bnk_stmt.balance_end
        return 0

    @api.multi
    def _set_opening_balance(self, journal_id):
        self.balance_start = self._get_opening_balance(journal_id)

    @api.model
    def _default_opening_balance(self):
        #Search last bank statement and set current opening balance as closing balance of previous one
        journal_id = self._context.get('default_journal_id',
                                       False) or self._context.get(
                                           'journal_id', False)
        if journal_id:
            return self._get_opening_balance(journal_id)
        return 0

    _name = "account.bank.statement"
    _description = "Bank Statement"
    _order = "date desc, id desc"
    _inherit = ['mail.thread']

    name = fields.Char(string='Reference',
                       states={'open': [('readonly', False)]},
                       copy=False,
                       readonly=True)
    reference = fields.Char(
        string='External Reference',
        states={'open': [('readonly', False)]},
        copy=False,
        readonly=True,
        help=
        "Used to hold the reference of the external mean that created this statement (name of imported file, reference of online synchronization...)"
    )
    date = fields.Date(required=True,
                       states={'confirm': [('readonly', True)]},
                       index=True,
                       copy=False,
                       default=fields.Date.context_today)
    date_done = fields.Datetime(string="Closed On")
    balance_start = fields.Monetary(string='Starting Balance',
                                    states={'confirm': [('readonly', True)]},
                                    default=_default_opening_balance)
    balance_end_real = fields.Monetary(
        'Ending Balance', states={'confirm': [('readonly', True)]})
    accounting_date = fields.Date(
        string="Accounting Date",
        help=
        "If set, the accounting entries created during the bank statement reconciliation process will be created at this date.\n"
        "This is useful if the accounting period in which the entries should normally be booked is already closed.",
        states={'open': [('readonly', False)]},
        readonly=True)
    state = fields.Selection([('open', 'New'), ('confirm', 'Validated')],
                             string='Status',
                             required=True,
                             readonly=True,
                             copy=False,
                             default='open')
    currency_id = fields.Many2one('res.currency',
                                  compute='_compute_currency',
                                  oldname='currency',
                                  string="Currency")
    journal_id = fields.Many2one('account.journal',
                                 string='Journal',
                                 required=True,
                                 states={'confirm': [('readonly', True)]},
                                 default=_default_journal)
    journal_type = fields.Selection(
        related='journal_id.type',
        help="Technical field used for usability purposes",
        readonly=False)
    company_id = fields.Many2one(
        'res.company',
        related='journal_id.company_id',
        string='Company',
        store=True,
        readonly=True,
        default=lambda self: self.env['res.company']._company_default_get(
            'account.bank.statement'))

    total_entry_encoding = fields.Monetary('Transactions Subtotal',
                                           compute='_end_balance',
                                           store=True,
                                           help="Total of transaction lines.")
    balance_end = fields.Monetary(
        'Computed Balance',
        compute='_end_balance',
        store=True,
        help=
        'Balance as calculated based on Opening Balance and transaction lines')
    difference = fields.Monetary(
        compute='_end_balance',
        store=True,
        help=
        "Difference between the computed ending balance and the specified ending balance."
    )

    line_ids = fields.One2many('account.bank.statement.line',
                               'statement_id',
                               string='Statement lines',
                               states={'confirm': [('readonly', True)]},
                               copy=True)
    move_line_ids = fields.One2many('account.move.line',
                                    'statement_id',
                                    string='Entry lines',
                                    states={'confirm': [('readonly', True)]})
    move_line_count = fields.Integer(compute="_get_move_line_count")

    all_lines_reconciled = fields.Boolean(compute='_check_lines_reconciled')
    user_id = fields.Many2one('res.users',
                              string='Responsible',
                              required=False,
                              default=lambda self: self.env.user)
    cashbox_start_id = fields.Many2one('account.bank.statement.cashbox',
                                       string="Starting Cashbox")
    cashbox_end_id = fields.Many2one('account.bank.statement.cashbox',
                                     string="Ending Cashbox")
    is_difference_zero = fields.Boolean(compute='_is_difference_zero',
                                        string='Is zero',
                                        help="Check if difference is zero.")

    @api.onchange('journal_id')
    def onchange_journal_id(self):
        self._set_opening_balance(self.journal_id.id)

    @api.multi
    def _balance_check(self):
        for stmt in self:
            if not stmt.currency_id.is_zero(stmt.difference):
                if stmt.journal_type == 'cash':
                    if stmt.difference < 0.0:
                        account = stmt.journal_id.loss_account_id
                        name = _('Loss')
                    else:
                        # statement.difference > 0.0
                        account = stmt.journal_id.profit_account_id
                        name = _('Profit')
                    if not account:
                        raise UserError(
                            _('There is no account defined on the journal %s for %s involved in a cash difference.'
                              ) % (stmt.journal_id.name, name))

                    values = {
                        'statement_id':
                        stmt.id,
                        'account_id':
                        account.id,
                        'amount':
                        stmt.difference,
                        'name':
                        _("Cash difference observed during the counting (%s)")
                        % name,
                    }
                    self.env['account.bank.statement.line'].create(values)
                else:
                    balance_end_real = formatLang(
                        self.env,
                        stmt.balance_end_real,
                        currency_obj=stmt.currency_id)
                    balance_end = formatLang(self.env,
                                             stmt.balance_end,
                                             currency_obj=stmt.currency_id)
                    raise UserError(
                        _('The ending balance is incorrect !\nThe expected balance (%s) is different from the computed one. (%s)'
                          ) % (balance_end_real, balance_end))
        return True

    @api.multi
    def unlink(self):
        for statement in self:
            if statement.state != 'open':
                raise UserError(
                    _('In order to delete a bank statement, you must first cancel it to delete related journal items.'
                      ))
            # Explicitly unlink bank statement lines so it will check that the related journal entries have been deleted first
            statement.line_ids.unlink()
        return super(AccountBankStatement, self).unlink()

    @api.multi
    def open_cashbox_id(self):
        context = dict(self.env.context or {})
        if context.get('cashbox_id'):
            context['active_id'] = self.id
            return {
                'name': _('Cash Control'),
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'account.bank.statement.cashbox',
                'view_id':
                self.env.ref('account.view_account_bnk_stmt_cashbox').id,
                'type': 'ir.actions.act_window',
                'res_id': self.env.context.get('cashbox_id'),
                'context': context,
                'target': 'new'
            }

    @api.multi
    def check_confirm_bank(self):
        if self.journal_type == 'cash' and not self.currency_id.is_zero(
                self.difference):
            action_rec = self.env['ir.model.data'].xmlid_to_object(
                'account.action_view_account_bnk_stmt_check')
            if action_rec:
                action = action_rec.read([])[0]
                return action
        return self.button_confirm_bank()

    @api.multi
    def button_confirm_bank(self):
        self._balance_check()
        statements = self.filtered(lambda r: r.state == 'open')
        for statement in statements:
            moves = self.env['account.move']
            # `line.journal_entry_ids` gets invalidated from the cache during the loop
            # because new move lines are being created at each iteration.
            # The below dict is to prevent the ORM to permanently refetch `line.journal_entry_ids`
            line_journal_entries = {
                line: line.journal_entry_ids
                for line in statement.line_ids
            }
            for st_line in statement.line_ids:
                #upon bank statement confirmation, look if some lines have the account_id set. It would trigger a journal entry
                #creation towards that account, with the wanted side-effect to skip that line in the bank reconciliation widget.
                journal_entries = line_journal_entries[st_line]
                st_line.fast_counterpart_creation()
                if not st_line.account_id and not journal_entries.ids and not st_line.statement_id.currency_id.is_zero(
                        st_line.amount):
                    raise UserError(
                        _('All the account entries lines must be processed in order to close the statement.'
                          ))
            moves = statement.mapped('line_ids.journal_entry_ids.move_id')
            if moves:
                moves.filtered(lambda m: m.state != 'posted').post()
            statement.message_post(
                body=_('Statement %s confirmed, journal items were created.') %
                (statement.name, ))
        statements.write({
            'state': 'confirm',
            'date_done': time.strftime("%Y-%m-%d %H:%M:%S")
        })

    @api.multi
    def button_journal_entries(self):
        return {
            'name':
            _('Journal Entries'),
            'view_type':
            'form',
            'view_mode':
            'tree,form',
            'res_model':
            'account.move',
            'view_id':
            False,
            'type':
            'ir.actions.act_window',
            'domain':
            [('id', 'in', self.mapped('move_line_ids').mapped('move_id').ids)],
            'context': {
                'journal_id': self.journal_id.id,
            }
        }

    @api.multi
    def button_open(self):
        """ Changes statement state to Running."""
        for statement in self:
            if not statement.name:
                context = {'ir_sequence_date': statement.date}
                if statement.journal_id.sequence_id:
                    st_number = statement.journal_id.sequence_id.with_context(
                        **context).next_by_id()
                else:
                    SequenceObj = self.env['ir.sequence']
                    st_number = SequenceObj.with_context(
                        **context).next_by_code('account.bank.statement')
                statement.name = st_number
            statement.state = 'open'
예제 #23
0
class HrContract(models.Model):
    _inherit = 'hr.contract'

    transport_mode_car = fields.Boolean('Uses company car')
    transport_mode_public = fields.Boolean('Uses public transportation')
    transport_mode_others = fields.Boolean('Uses another transport mode')
    car_atn = fields.Monetary(string='ATN Company Car')
    public_transport_employee_amount = fields.Monetary(
        'Paid by the employee (Monthly)')
    thirteen_month = fields.Monetary(
        compute='_compute_holidays_advantages',
        string='13th Month',
        help="Yearly gross amount the employee receives as 13th month bonus.")
    double_holidays = fields.Monetary(
        compute='_compute_holidays_advantages',
        string='Holiday Bonus',
        help="Yearly gross amount the employee receives as holidays bonus.")
    warrant_value_employee = fields.Monetary(
        compute='_compute_warrants_cost',
        string="Warrant value for the employee")

    # Employer costs fields
    final_yearly_costs = fields.Monetary(
        compute='_compute_final_yearly_costs',
        readonly=False,
        store=True,
        string="Employee Budget",
        track_visibility="onchange",
        help="Total yearly cost of the employee for the employer.")
    monthly_yearly_costs = fields.Monetary(
        compute='_compute_monthly_yearly_costs',
        string='Monthly Equivalent Cost',
        readonly=True,
        help="Total monthly cost of the employee for the employer.")
    ucm_insurance = fields.Monetary(compute='_compute_ucm_insurance',
                                    string="Social Secretary Costs")
    social_security_contributions = fields.Monetary(
        compute='_compute_social_security_contributions',
        string="Social Security Contributions")
    yearly_cost_before_charges = fields.Monetary(
        compute='_compute_yearly_cost_before_charges',
        string="Yearly Costs Before Charges")
    meal_voucher_paid_by_employer = fields.Monetary(
        compute='_compute_meal_voucher_paid_by_employer',
        string="Meal Voucher Paid by Employer")
    company_car_total_depreciated_cost = fields.Monetary()
    public_transport_reimbursed_amount = fields.Monetary(
        string='Reimbursed amount',
        compute='_compute_public_transport_reimbursed_amount',
        readonly=False,
        store=True)
    others_reimbursed_amount = fields.Monetary(
        string='Other Reimbursed amount')
    transport_employer_cost = fields.Monetary(
        compute='_compute_transport_employer_cost',
        string="Employer cost from employee transports")
    warrants_cost = fields.Monetary(compute='_compute_warrants_cost')

    # Advantages
    commission_on_target = fields.Monetary(
        string="Commission on Target",
        default=lambda self: self.get_attribute('commission_on_target',
                                                'default_value'),
        track_visibility="onchange",
        help=
        "Monthly gross amount that the employee receives if the target is reached."
    )
    fuel_card = fields.Monetary(
        string="Fuel Card",
        default=lambda self: self.get_attribute('fuel_card', 'default_value'),
        track_visibility="onchange",
        help="Monthly amount the employee receives on his fuel card.")
    internet = fields.Monetary(
        string="Internet",
        default=lambda self: self.get_attribute('internet', 'default_value'),
        track_visibility="onchange",
        help=
        "The employee's internet subcription will be paid up to this amount.")
    representation_fees = fields.Monetary(
        string="Representation Fees",
        default=lambda self: self.get_attribute('representation_fees',
                                                'default_value'),
        track_visibility="onchange",
        help=
        "Monthly net amount the employee receives to cover his representation fees."
    )
    mobile = fields.Monetary(
        string="Mobile",
        default=lambda self: self.get_attribute('mobile', 'default_value'),
        track_visibility="onchange",
        help=
        "The employee's mobile subscription will be paid up to this amount.")
    mobile_plus = fields.Monetary(
        string="International Communication",
        default=lambda self: self.get_attribute('mobile_plus', 'default_value'
                                                ),
        track_visibility="onchange",
        help=
        "The employee's mobile subscription for international communication will be paid up to this amount."
    )
    meal_voucher_amount = fields.Monetary(
        string="Meal Vouchers",
        default=lambda self: self.get_attribute('meal_voucher_amount',
                                                'default_value'),
        track_visibility="onchange",
        help=
        "Amount the employee receives in the form of meal vouchers per worked day."
    )
    holidays = fields.Float(
        string='Legal Leaves',
        default=lambda self: self.get_attribute('holidays', 'default_value'),
        help="Number of days of paid leaves the employee gets per year.")
    holidays_editable = fields.Boolean(string="Editable Leaves", default=True)
    holidays_compensation = fields.Monetary(
        compute='_compute_holidays_compensation',
        string="Holidays Compensation")
    wage_with_holidays = fields.Monetary(
        compute='_compute_wage_with_holidays',
        inverse='_inverse_wage_with_holidays',
        track_visibility='onchange',
        string="Wage update with holidays retenues")
    additional_net_amount = fields.Monetary(
        string="Net Supplements",
        track_visibility="onchange",
        help="Monthly net amount the employee receives.")
    retained_net_amount = fields.Monetary(
        sting="Net Retained",
        track_visibility="onchange",
        help="Monthly net amount that is retained on the employee's salary.")
    eco_checks = fields.Monetary(
        "Eco Vouchers",
        default=lambda self: self.get_attribute('eco_checks', 'default_value'),
        help="Yearly amount the employee receives in the form of eco vouchers."
    )
    ip = fields.Boolean(default=False, track_visibility="onchange")
    ip_wage_rate = fields.Float(string="IP percentage",
                                help="Should be between 0 and 100 %")

    @api.constrains('ip_wage_rate')
    def _check_ip_wage_rate(self):
        if self.filtered(lambda contract: contract.ip_wage_rate < 0 or contract
                         .ip_wage_rate > 100):
            raise ValidationError(
                _('The IP rate on wage should be between 0 and 100'))

    @api.depends('holidays', 'wage', 'final_yearly_costs')
    def _compute_wage_with_holidays(self):
        for contract in self:
            if contract.holidays > 20.0:
                yearly_cost = contract.final_yearly_costs * (
                    1.0 - (contract.holidays - 20.0) / 231.0)
                contract.wage_with_holidays = contract._get_gross_from_employer_costs(
                    yearly_cost)
            else:
                contract.wage_with_holidays = contract.wage

    def _inverse_wage_with_holidays(self):
        for contract in self:
            if contract.holidays > 20.0:
                remaining_for_gross = contract.wage_with_holidays * (
                    13.0 + 13.0 * 0.3507 + 0.92)
                yearly_cost = remaining_for_gross \
                    + 12.0 * contract.representation_fees \
                    + 12.0 * contract.fuel_card \
                    + 12.0 * contract.internet \
                    + 12.0 * (contract.mobile + contract.mobile_plus) \
                    + 12.0 * contract.transport_employer_cost \
                    + contract.warrants_cost \
                    + 220.0 * contract.meal_voucher_paid_by_employer
                contract.final_yearly_costs = yearly_cost / (
                    1.0 - (contract.holidays - 20.0) / 231.0)
                contract.wage = contract._get_gross_from_employer_costs(
                    contract.final_yearly_costs)
            else:
                contract.wage = contract.wage_with_holidays

    @api.depends('transport_mode_car', 'transport_mode_public',
                 'transport_mode_others', 'company_car_total_depreciated_cost',
                 'public_transport_reimbursed_amount',
                 'others_reimbursed_amount')
    def _compute_transport_employer_cost(self):
        # Don't call to super has we ovewrite the method
        for contract in self:
            transport_employer_cost = 0.0
            if contract.transport_mode_car:
                transport_employer_cost += contract.company_car_total_depreciated_cost
            if contract.transport_mode_public:
                transport_employer_cost += contract.public_transport_reimbursed_amount
            if contract.transport_mode_others:
                transport_employer_cost += contract.others_reimbursed_amount
            contract.transport_employer_cost = transport_employer_cost

    @api.depends('commission_on_target')
    def _compute_warrants_cost(self):
        for contract in self:
            contract.warrants_cost = contract.commission_on_target * 1.326 / 1.05 * 12.0
            contract.warrant_value_employee = contract.commission_on_target * 1.326 * (
                1.00 - 0.535) * 12.0

    @api.depends('wage', 'fuel_card', 'representation_fees',
                 'transport_employer_cost', 'internet', 'mobile',
                 'mobile_plus')
    def _compute_yearly_cost_before_charges(self):
        for contract in self:
            contract.yearly_cost_before_charges = 12.0 * (
                contract.wage * (1.0 + 1.0 / 12.0) + contract.fuel_card +
                contract.representation_fees + contract.internet +
                contract.mobile + contract.mobile_plus +
                contract.transport_employer_cost)

    @api.depends('yearly_cost_before_charges', 'social_security_contributions',
                 'wage', 'social_security_contributions', 'warrants_cost',
                 'meal_voucher_paid_by_employer')
    def _compute_final_yearly_costs(self):
        for contract in self:
            contract.final_yearly_costs = (
                contract.yearly_cost_before_charges +
                contract.social_security_contributions + contract.wage * 0.92 +
                contract.warrants_cost +
                (220.0 * contract.meal_voucher_paid_by_employer))

    @api.depends('holidays', 'final_yearly_costs')
    def _compute_holidays_compensation(self):
        for contract in self:
            if contract.holidays < 20:
                decrease_amount = contract.final_yearly_costs * (
                    20.0 - contract.holidays) / 231.0
                contract.holidays_compensation = decrease_amount
            else:
                contract.holidays_compensation = 0.0

    @api.onchange('final_yearly_costs')
    def _onchange_final_yearly_costs(self):
        self.wage = self._get_gross_from_employer_costs(
            self.final_yearly_costs)

    @api.depends('meal_voucher_amount')
    def _compute_meal_voucher_paid_by_employer(self):
        for contract in self:
            contract.meal_voucher_paid_by_employer = contract.meal_voucher_amount * (
                1 - 0.1463)

    @api.depends('wage')
    def _compute_social_security_contributions(self):
        for contract in self:
            total_wage = contract.wage * 13.0
            contract.social_security_contributions = (total_wage) * 0.3507

    @api.depends('wage')
    def _compute_ucm_insurance(self):
        for contract in self:
            contract.ucm_insurance = (contract.wage * 12.0) * 0.05

    @api.depends('public_transport_employee_amount')
    def _compute_public_transport_reimbursed_amount(self):
        for contract in self:
            contract.public_transport_reimbursed_amount = contract._get_public_transport_reimbursed_amount(
                contract.public_transport_employee_amount)

    def _get_public_transport_reimbursed_amount(self, amount):
        return amount * 0.68

    @api.depends('final_yearly_costs')
    def _compute_monthly_yearly_costs(self):
        for contract in self:
            contract.monthly_yearly_costs = contract.final_yearly_costs / 12.0

    @api.depends('wage_with_holidays')
    def _compute_holidays_advantages(self):
        for contract in self:
            contract.double_holidays = contract.wage_with_holidays * 0.92
            contract.thirteen_month = contract.wage_with_holidays

    @api.onchange('transport_mode_car', 'transport_mode_public',
                  'transport_mode_others')
    def _onchange_transport_mode(self):
        if not self.transport_mode_car:
            self.fuel_card = 0
            self.company_car_total_depreciated_cost = 0
        if not self.transport_mode_others:
            self.others_reimbursed_amount = 0
        if not self.transport_mode_public:
            self.public_transport_reimbursed_amount = 0

    @api.onchange('mobile', 'mobile_plus')
    def _onchange_mobile(self):
        if self.mobile_plus and not self.mobile:
            raise ValidationError(
                _('You should have a mobile subscription to select an international communication amount.'
                  ))

    def _get_internet_amount(self, has_internet):
        if has_internet:
            return self.get_attribute('internet', 'default_value')
        else:
            return 0.0

    def _get_mobile_amount(self, has_mobile, international_communication):
        if has_mobile and international_communication:
            return self.get_attribute('mobile',
                                      'default_value') + self.get_attribute(
                                          'mobile_plus', 'default_value')
        elif has_mobile:
            return self.get_attribute('mobile', 'default_value')
        else:
            return 0.0

    def _get_gross_from_employer_costs(self, yearly_cost):
        contract = self
        remaining_for_gross = yearly_cost \
            - 12.0 * contract.representation_fees \
            - 12.0 * contract.fuel_card \
            - 12.0 * contract.internet \
            - 12.0 * (contract.mobile + contract.mobile_plus) \
            - 12.0 * contract.transport_employer_cost \
            - contract.warrants_cost \
            - 220.0 * contract.meal_voucher_paid_by_employer
        gross = remaining_for_gross / (13.0 + 13.0 * 0.3507 + 0.92)
        return gross
예제 #24
0
파일: purchase.py 프로젝트: metricsw/swerp
class PurchaseOrder(models.Model):
    _name = "purchase.order"
    _inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin']
    _description = "Purchase Order"
    _order = 'date_order desc, id desc'

    def _default_currency_id(self):
        company_id = self.env.context.get(
            'force_company') or self.env.context.get(
                'company_id') or self.env.user.company_id.id
        return self.env['res.company'].browse(company_id).currency_id

    @api.depends('order_line.price_total')
    def _amount_all(self):
        for order in self:
            amount_untaxed = amount_tax = 0.0
            for line in order.order_line:
                amount_untaxed += line.price_subtotal
                amount_tax += line.price_tax
            order.update({
                'amount_untaxed':
                order.currency_id.round(amount_untaxed),
                'amount_tax':
                order.currency_id.round(amount_tax),
                'amount_total':
                amount_untaxed + amount_tax,
            })

    @api.depends('order_line.date_planned', 'date_order')
    def _compute_date_planned(self):
        for order in self:
            min_date = False
            for line in order.order_line:
                if not min_date or line.date_planned < min_date:
                    min_date = line.date_planned
            if min_date:
                order.date_planned = min_date
            else:
                order.date_planned = order.date_order

    @api.depends('state', 'order_line.qty_invoiced', 'order_line.qty_received',
                 'order_line.product_qty')
    def _get_invoiced(self):
        precision = self.env['decimal.precision'].precision_get(
            'Product Unit of Measure')
        for order in self:
            if order.state not in ('purchase', 'done'):
                order.invoice_status = 'no'
                continue

            if any(
                    float_compare(
                        line.qty_invoiced,
                        line.product_qty if line.product_id.purchase_method ==
                        'purchase' else line.qty_received,
                        precision_digits=precision) == -1
                    for line in order.order_line):
                order.invoice_status = 'to invoice'
            elif all(
                    float_compare(
                        line.qty_invoiced,
                        line.product_qty if line.product_id.purchase_method ==
                        'purchase' else line.qty_received,
                        precision_digits=precision) >= 0
                    for line in order.order_line) and order.invoice_ids:
                order.invoice_status = 'invoiced'
            else:
                order.invoice_status = 'no'

    @api.depends('order_line.invoice_lines.invoice_id')
    def _compute_invoice(self):
        for order in self:
            invoices = self.env['account.invoice']
            for line in order.order_line:
                invoices |= line.invoice_lines.mapped('invoice_id')
            order.invoice_ids = invoices
            order.invoice_count = len(invoices)

    READONLY_STATES = {
        'purchase': [('readonly', True)],
        'done': [('readonly', True)],
        'cancel': [('readonly', True)],
    }

    name = fields.Char('Order Reference',
                       required=True,
                       index=True,
                       copy=False,
                       default='New')
    origin = fields.Char(
        'Source Document',
        copy=False,
        help="Reference of the document that generated this purchase order "
        "request (e.g. a sales order)")
    partner_ref = fields.Char(
        'Vendor Reference',
        copy=False,
        help="Reference of the sales order or bid sent by the vendor. "
        "It's used to do the matching when you receive the "
        "products as this reference is usually written on the "
        "delivery order sent by your vendor.")
    date_order = fields.Datetime('Order Date', required=True, states=READONLY_STATES, index=True, copy=False, default=fields.Datetime.now,\
        help="Depicts the date where the Quotation should be validated and converted into a purchase order.")
    date_approve = fields.Date('Approval Date',
                               readonly=1,
                               index=True,
                               copy=False)
    partner_id = fields.Many2one(
        'res.partner',
        string='Vendor',
        required=True,
        states=READONLY_STATES,
        change_default=True,
        track_visibility='always',
        help=
        "You can find a vendor by its Name, TIN, Email or Internal Reference.")
    dest_address_id = fields.Many2one(
        'res.partner',
        string='Drop Ship Address',
        states=READONLY_STATES,
        help=
        "Put an address if you want to deliver directly from the vendor to the customer. "
        "Otherwise, keep empty to deliver to your own company.")
    currency_id = fields.Many2one('res.currency',
                                  'Currency',
                                  required=True,
                                  states=READONLY_STATES,
                                  default=_default_currency_id)
    state = fields.Selection([('draft', 'RFQ'), ('sent', 'RFQ Sent'),
                              ('to approve', 'To Approve'),
                              ('purchase', 'Purchase Order'),
                              ('done', 'Locked'), ('cancel', 'Cancelled')],
                             string='Status',
                             readonly=True,
                             index=True,
                             copy=False,
                             default='draft',
                             track_visibility='onchange')
    order_line = fields.One2many('purchase.order.line',
                                 'order_id',
                                 string='Order Lines',
                                 states={
                                     'cancel': [('readonly', True)],
                                     'done': [('readonly', True)]
                                 },
                                 copy=True)
    notes = fields.Text('Terms and Conditions')

    invoice_count = fields.Integer(compute="_compute_invoice",
                                   string='Bill Count',
                                   copy=False,
                                   default=0,
                                   store=True)
    invoice_ids = fields.Many2many('account.invoice',
                                   compute="_compute_invoice",
                                   string='Bills',
                                   copy=False,
                                   store=True)
    invoice_status = fields.Selection([
        ('no', 'Nothing to Bill'),
        ('to invoice', 'Waiting Bills'),
        ('invoiced', 'No Bill to Receive'),
    ],
                                      string='Billing Status',
                                      compute='_get_invoiced',
                                      store=True,
                                      readonly=True,
                                      copy=False,
                                      default='no')

    # There is no inverse function on purpose since the date may be different on each line
    date_planned = fields.Datetime(string='Scheduled Date',
                                   compute='_compute_date_planned',
                                   store=True,
                                   index=True)

    amount_untaxed = fields.Monetary(string='Untaxed Amount',
                                     store=True,
                                     readonly=True,
                                     compute='_amount_all',
                                     track_visibility='always')
    amount_tax = fields.Monetary(string='Taxes',
                                 store=True,
                                 readonly=True,
                                 compute='_amount_all')
    amount_total = fields.Monetary(string='Total',
                                   store=True,
                                   readonly=True,
                                   compute='_amount_all')

    fiscal_position_id = fields.Many2one('account.fiscal.position',
                                         string='Fiscal Position',
                                         oldname='fiscal_position')
    payment_term_id = fields.Many2one('account.payment.term', 'Payment Terms')
    incoterm_id = fields.Many2one(
        'account.incoterms',
        'Incoterm',
        states={'done': [('readonly', True)]},
        help=
        "International Commercial Terms are a series of predefined commercial terms used in international transactions."
    )

    product_id = fields.Many2one('product.product',
                                 related='order_line.product_id',
                                 string='Product',
                                 readonly=False)
    user_id = fields.Many2one('res.users',
                              string='Purchase Representative',
                              index=True,
                              track_visibility='onchange',
                              default=lambda self: self.env.user)
    company_id = fields.Many2one(
        'res.company',
        'Company',
        required=True,
        index=True,
        states=READONLY_STATES,
        default=lambda self: self.env.user.company_id.id)

    def _compute_access_url(self):
        super(PurchaseOrder, self)._compute_access_url()
        for order in self:
            order.access_url = '/my/purchase/%s' % (order.id)

    @api.model
    def _name_search(self,
                     name,
                     args=None,
                     operator='ilike',
                     limit=100,
                     name_get_uid=None):
        args = args or []
        domain = []
        if name:
            domain = [
                '|', ('name', operator, name), ('partner_ref', operator, name)
            ]
        purchase_order_ids = self._search(expression.AND([domain, args]),
                                          limit=limit,
                                          access_rights_uid=name_get_uid)
        return self.browse(purchase_order_ids).name_get()

    @api.multi
    @api.depends('name', 'partner_ref')
    def name_get(self):
        result = []
        for po in self:
            name = po.name
            if po.partner_ref:
                name += ' (' + po.partner_ref + ')'
            if self.env.context.get('show_total_amount') and po.amount_total:
                name += ': ' + formatLang(
                    self.env, po.amount_total, currency_obj=po.currency_id)
            result.append((po.id, name))
        return result

    @api.model
    def create(self, vals):
        company_id = vals.get('company_id',
                              self.default_get(['company_id'])['company_id'])
        if vals.get('name', 'New') == 'New':
            vals['name'] = self.env['ir.sequence'].with_context(
                force_company=company_id).next_by_code('purchase.order') or '/'
        return super(PurchaseOrder,
                     self.with_context(company_id=company_id)).create(vals)

    @api.multi
    def unlink(self):
        for order in self:
            if not order.state == 'cancel':
                raise UserError(
                    _('In order to delete a purchase order, you must cancel it first.'
                      ))
        return super(PurchaseOrder, self).unlink()

    @api.multi
    def copy(self, default=None):
        ctx = dict(self.env.context)
        ctx.pop('default_product_id', None)
        self = self.with_context(ctx)
        new_po = super(PurchaseOrder, self).copy(default=default)
        for line in new_po.order_line:
            seller = line.product_id._select_seller(
                partner_id=line.partner_id,
                quantity=line.product_qty,
                date=line.order_id.date_order
                and line.order_id.date_order.date(),
                uom_id=line.product_uom)
            line.date_planned = line._get_date_planned(seller)
        return new_po

    @api.multi
    def _track_subtype(self, init_values):
        self.ensure_one()
        if 'state' in init_values and self.state == 'purchase':
            return 'purchase.mt_rfq_approved'
        elif 'state' in init_values and self.state == 'to approve':
            return 'purchase.mt_rfq_confirmed'
        elif 'state' in init_values and self.state == 'done':
            return 'purchase.mt_rfq_done'
        return super(PurchaseOrder, self)._track_subtype(init_values)

    @api.onchange('partner_id', 'company_id')
    def onchange_partner_id(self):
        if not self.partner_id:
            self.fiscal_position_id = False
            self.payment_term_id = False
            self.currency_id = self.env.user.company_id.currency_id.id
        else:
            self.fiscal_position_id = self.env[
                'account.fiscal.position'].with_context(
                    company_id=self.company_id.id).get_fiscal_position(
                        self.partner_id.id)
            self.payment_term_id = self.partner_id.property_supplier_payment_term_id.id
            self.currency_id = self.partner_id.property_purchase_currency_id.id or self.env.user.company_id.currency_id.id
        return {}

    @api.onchange('fiscal_position_id')
    def _compute_tax_id(self):
        """
        Trigger the recompute of the taxes if the fiscal position is changed on the PO.
        """
        for order in self:
            order.order_line._compute_tax_id()

    @api.onchange('partner_id')
    def onchange_partner_id_warning(self):
        if not self.partner_id:
            return
        warning = {}
        title = False
        message = False

        partner = self.partner_id

        # If partner has no warning, check its company
        if partner.purchase_warn == 'no-message' and partner.parent_id:
            partner = partner.parent_id

        if partner.purchase_warn and partner.purchase_warn != 'no-message':
            # Block if partner only has warning but parent company is blocked
            if partner.purchase_warn != 'block' and partner.parent_id and partner.parent_id.purchase_warn == 'block':
                partner = partner.parent_id
            title = _("Warning for %s") % partner.name
            message = partner.purchase_warn_msg
            warning = {'title': title, 'message': message}
            if partner.purchase_warn == 'block':
                self.update({'partner_id': False})
            return {'warning': warning}
        return {}

    @api.multi
    def action_rfq_send(self):
        '''
        This function opens a window to compose an email, with the edi purchase template message loaded by default
        '''
        self.ensure_one()
        ir_model_data = self.env['ir.model.data']
        try:
            if self.env.context.get('send_rfq', False):
                template_id = ir_model_data.get_object_reference(
                    'purchase', 'email_template_edi_purchase')[1]
            else:
                template_id = ir_model_data.get_object_reference(
                    'purchase', 'email_template_edi_purchase_done')[1]
        except ValueError:
            template_id = False
        try:
            compose_form_id = ir_model_data.get_object_reference(
                'mail', 'email_compose_message_wizard_form')[1]
        except ValueError:
            compose_form_id = False
        ctx = dict(self.env.context or {})
        ctx.update({
            'default_model': 'purchase.order',
            'default_res_id': self.ids[0],
            'default_use_template': bool(template_id),
            'default_template_id': template_id,
            'default_composition_mode': 'comment',
            'custom_layout': "mail.mail_notification_paynow",
            'force_email': True,
            'mark_rfq_as_sent': True,
        })

        # In the case of a RFQ or a PO, we want the "View..." button in line with the state of the
        # object. Therefore, we pass the model description in the context, in the language in which
        # the template is rendered.
        lang = self.env.context.get('lang')
        if {'default_template_id', 'default_model', 'default_res_id'
            } <= ctx.keys():
            template = self.env['mail.template'].browse(
                ctx['default_template_id'])
            if template and template.lang:
                lang = template._render_template(template.lang,
                                                 ctx['default_model'],
                                                 ctx['default_res_id'])

        self = self.with_context(lang=lang)
        if self.state in ['draft', 'sent']:
            ctx['model_description'] = _('Request for Quotation')
        else:
            ctx['model_description'] = _('Purchase Order')

        return {
            'name': _('Compose Email'),
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'mail.compose.message',
            'views': [(compose_form_id, 'form')],
            'view_id': compose_form_id,
            'target': 'new',
            'context': ctx,
        }

    @api.multi
    @api.returns('mail.message', lambda value: value.id)
    def message_post(self, **kwargs):
        if self.env.context.get('mark_rfq_as_sent'):
            self.filtered(lambda o: o.state == 'draft').write(
                {'state': 'sent'})
        return super(PurchaseOrder, self.with_context(
            mail_post_autofollow=True)).message_post(**kwargs)

    @api.multi
    def print_quotation(self):
        self.write({'state': "sent"})
        return self.env.ref(
            'purchase.report_purchase_quotation').report_action(self)

    @api.multi
    def button_approve(self, force=False):
        self.write({
            'state': 'purchase',
            'date_approve': fields.Date.context_today(self)
        })
        self.filtered(lambda p: p.company_id.po_lock == 'lock').write(
            {'state': 'done'})
        return {}

    @api.multi
    def button_draft(self):
        self.write({'state': 'draft'})
        return {}

    @api.multi
    def button_confirm(self):
        for order in self:
            if order.state not in ['draft', 'sent']:
                continue
            order._add_supplier_to_product()
            # Deal with double validation process
            if order.company_id.po_double_validation == 'one_step'\
                    or (order.company_id.po_double_validation == 'two_step'\
                        and order.amount_total < self.env.user.company_id.currency_id._convert(
                            order.company_id.po_double_validation_amount, order.currency_id, order.company_id, order.date_order or fields.Date.today()))\
                    or order.user_has_groups('purchase.group_purchase_manager'):
                order.button_approve()
            else:
                order.write({'state': 'to approve'})
        return True

    @api.multi
    def button_cancel(self):
        for order in self:
            for inv in order.invoice_ids:
                if inv and inv.state not in ('cancel', 'draft'):
                    raise UserError(
                        _("Unable to cancel this purchase order. You must first cancel the related vendor bills."
                          ))

        self.write({'state': 'cancel'})

    @api.multi
    def button_unlock(self):
        self.write({'state': 'purchase'})

    @api.multi
    def button_done(self):
        self.write({'state': 'done'})

    @api.multi
    def _add_supplier_to_product(self):
        # Add the partner in the supplier list of the product if the supplier is not registered for
        # this product. We limit to 10 the number of suppliers for a product to avoid the mess that
        # could be caused for some generic products ("Miscellaneous").
        for line in self.order_line:
            # Do not add a contact as a supplier
            partner = self.partner_id if not self.partner_id.parent_id else self.partner_id.parent_id
            if partner not in line.product_id.seller_ids.mapped(
                    'name') and len(line.product_id.seller_ids) <= 10:
                # Convert the price in the right currency.
                currency = partner.property_purchase_currency_id or self.env.user.company_id.currency_id
                price = self.currency_id._convert(line.price_unit,
                                                  currency,
                                                  line.company_id,
                                                  line.date_order
                                                  or fields.Date.today(),
                                                  round=False)
                # Compute the price for the template's UoM, because the supplier's UoM is related to that UoM.
                if line.product_id.product_tmpl_id.uom_po_id != line.product_uom:
                    default_uom = line.product_id.product_tmpl_id.uom_po_id
                    price = line.product_uom._compute_price(price, default_uom)

                supplierinfo = {
                    'name':
                    partner.id,
                    'sequence':
                    max(line.product_id.seller_ids.mapped('sequence')) +
                    1 if line.product_id.seller_ids else 1,
                    'min_qty':
                    0.0,
                    'price':
                    price,
                    'currency_id':
                    currency.id,
                    'delay':
                    0,
                }
                # In case the order partner is a contact address, a new supplierinfo is created on
                # the parent company. In this case, we keep the product name and code.
                seller = line.product_id._select_seller(
                    partner_id=line.partner_id,
                    quantity=line.product_qty,
                    date=line.order_id.date_order
                    and line.order_id.date_order.date(),
                    uom_id=line.product_uom)
                if seller:
                    supplierinfo['product_name'] = seller.product_name
                    supplierinfo['product_code'] = seller.product_code
                vals = {
                    'seller_ids': [(0, 0, supplierinfo)],
                }
                try:
                    line.product_id.write(vals)
                except AccessError:  # no write access rights -> just ignore
                    break

    @api.multi
    def action_view_invoice(self):
        '''
        This function returns an action that display existing vendor bills of given purchase order ids.
        When only one found, show the vendor bill immediately.
        '''
        action = self.env.ref('account.action_vendor_bill_template')
        result = action.read()[0]
        create_bill = self.env.context.get('create_bill', False)
        # override the context to get rid of the default filtering
        result['context'] = {
            'type': 'in_invoice',
            'default_purchase_id': self.id,
            'default_currency_id': self.currency_id.id,
            'default_company_id': self.company_id.id,
            'company_id': self.company_id.id
        }
        # choose the view_mode accordingly
        if len(self.invoice_ids) > 1 and not create_bill:
            result['domain'] = "[('id', 'in', " + str(
                self.invoice_ids.ids) + ")]"
        else:
            res = self.env.ref('account.invoice_supplier_form', False)
            form_view = [(res and res.id or False, 'form')]
            if 'views' in result:
                result['views'] = form_view + [
                    (state, view)
                    for state, view in action['views'] if view != 'form'
                ]
            else:
                result['views'] = form_view
            # Do not set an invoice_id if we want to create a new bill.
            if not create_bill:
                result['res_id'] = self.invoice_ids.id or False
        result['context']['default_origin'] = self.name
        result['context']['default_reference'] = self.partner_ref
        return result

    @api.multi
    def action_set_date_planned(self):
        for order in self:
            order.order_line.update({'date_planned': order.date_planned})
예제 #25
0
class HrExpense(models.Model):

    _name = "hr.expense"
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _description = "Expense"
    _order = "date desc, id desc"

    @api.model
    def _default_employee_id(self):
        return self.env['hr.employee'].search([('user_id', '=', self.env.uid)],
                                              limit=1)

    @api.model
    def _default_product_uom_id(self):
        return self.env['uom.uom'].search([], limit=1, order='id')

    @api.model
    def _default_account_id(self):
        return self.env['ir.property'].get('property_account_expense_categ_id',
                                           'product.category')

    @api.model
    def _get_employee_id_domain(self):
        res = [('id', '=', 0)]  # Nothing accepted by domain, by default
        if self.user_has_groups(
                'hr_expense.group_hr_expense_manager') or self.user_has_groups(
                    'account.group_account_user'):
            res = []  # Then, domain accepts everything
        elif self.user_has_groups('hr_expense.group_hr_expense_user'
                                  ) and self.env.user.employee_ids:
            user = self.env.user
            employee = user.employee_ids[0]
            res = [
                '|',
                '|',
                '|',
                ('department_id.manager_id', '=', employee.id),
                ('parent_id', '=', employee.id),
                ('id', '=', employee.id),
                ('expense_manager_id', '=', user.id),
            ]
        elif self.env.user.employee_ids:
            employee = self.env.user.employee_ids[0]
            res = [('id', '=', employee.id)]
        return res

    name = fields.Char('Description',
                       readonly=True,
                       required=True,
                       states={
                           'draft': [('readonly', False)],
                           'reported': [('readonly', False)],
                           'refused': [('readonly', False)]
                       })
    date = fields.Date(readonly=True,
                       states={
                           'draft': [('readonly', False)],
                           'reported': [('readonly', False)],
                           'refused': [('readonly', False)]
                       },
                       default=fields.Date.context_today,
                       string="Date")
    employee_id = fields.Many2one(
        'hr.employee',
        string="Employee",
        required=True,
        readonly=True,
        states={
            'draft': [('readonly', False)],
            'reported': [('readonly', False)],
            'refused': [('readonly', False)]
        },
        default=_default_employee_id,
        domain=lambda self: self._get_employee_id_domain())
    product_id = fields.Many2one('product.product',
                                 string='Product',
                                 readonly=True,
                                 states={
                                     'draft': [('readonly', False)],
                                     'reported': [('readonly', False)],
                                     'refused': [('readonly', False)]
                                 },
                                 domain=[('can_be_expensed', '=', True)],
                                 required=True)
    product_uom_id = fields.Many2one('uom.uom',
                                     string='Unit of Measure',
                                     required=True,
                                     readonly=True,
                                     states={
                                         'draft': [('readonly', False)],
                                         'refused': [('readonly', False)]
                                     },
                                     default=_default_product_uom_id)
    unit_amount = fields.Float("Unit Price",
                               readonly=True,
                               required=True,
                               states={
                                   'draft': [('readonly', False)],
                                   'reported': [('readonly', False)],
                                   'refused': [('readonly', False)]
                               },
                               digits=dp.get_precision('Product Price'))
    quantity = fields.Float(required=True,
                            readonly=True,
                            states={
                                'draft': [('readonly', False)],
                                'reported': [('readonly', False)],
                                'refused': [('readonly', False)]
                            },
                            digits=dp.get_precision('Product Unit of Measure'),
                            default=1)
    tax_ids = fields.Many2many('account.tax',
                               'expense_tax',
                               'expense_id',
                               'tax_id',
                               string='Taxes',
                               states={
                                   'done': [('readonly', True)],
                                   'post': [('readonly', True)]
                               })
    untaxed_amount = fields.Float("Subtotal",
                                  store=True,
                                  compute='_compute_amount',
                                  digits=dp.get_precision('Account'))
    total_amount = fields.Monetary("Total",
                                   compute='_compute_amount',
                                   store=True,
                                   currency_field='currency_id',
                                   digits=dp.get_precision('Account'))
    company_currency_id = fields.Many2one('res.currency',
                                          string="Report Company Currency",
                                          related='sheet_id.currency_id',
                                          store=True,
                                          readonly=False)
    total_amount_company = fields.Monetary(
        "Total (Company Currency)",
        compute='_compute_total_amount_company',
        store=True,
        currency_field='company_currency_id',
        digits=dp.get_precision('Account'))
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 readonly=True,
                                 states={
                                     'draft': [('readonly', False)],
                                     'refused': [('readonly', False)]
                                 },
                                 default=lambda self: self.env.user.company_id)
    currency_id = fields.Many2one(
        'res.currency',
        string='Currency',
        readonly=True,
        states={
            'draft': [('readonly', False)],
            'refused': [('readonly', False)]
        },
        default=lambda self: self.env.user.company_id.currency_id)
    analytic_account_id = fields.Many2one('account.analytic.account',
                                          string='Analytic Account',
                                          states={
                                              'post': [('readonly', True)],
                                              'done': [('readonly', True)]
                                          },
                                          oldname='analytic_account')
    analytic_tag_ids = fields.Many2many('account.analytic.tag',
                                        string='Analytic Tags',
                                        states={
                                            'post': [('readonly', True)],
                                            'done': [('readonly', True)]
                                        })
    account_id = fields.Many2one('account.account',
                                 string='Account',
                                 states={
                                     'post': [('readonly', True)],
                                     'done': [('readonly', True)]
                                 },
                                 default=_default_account_id,
                                 help="An expense account is expected")
    description = fields.Text('Notes...',
                              readonly=True,
                              states={
                                  'draft': [('readonly', False)],
                                  'reported': [('readonly', False)],
                                  'refused': [('readonly', False)]
                              })
    payment_mode = fields.Selection(
        [("own_account", "Employee (to reimburse)"),
         ("company_account", "Company")],
        default='own_account',
        states={
            'done': [('readonly', True)],
            'approved': [('readonly', True)],
            'reported': [('readonly', True)]
        },
        string="Paid By")
    attachment_number = fields.Integer('Number of Attachments',
                                       compute='_compute_attachment_number')
    state = fields.Selection([('draft', 'To Submit'),
                              ('reported', 'Submitted'),
                              ('approved', 'Approved'), ('done', 'Paid'),
                              ('refused', 'Refused')],
                             compute='_compute_state',
                             string='Status',
                             copy=False,
                             index=True,
                             readonly=True,
                             store=True,
                             help="Status of the expense.")
    sheet_id = fields.Many2one('hr.expense.sheet',
                               string="Expense Report",
                               readonly=True,
                               copy=False)
    reference = fields.Char("Bill Reference")
    is_refused = fields.Boolean(
        "Explicitely Refused by manager or acccountant",
        readonly=True,
        copy=False)

    @api.depends('sheet_id', 'sheet_id.account_move_id', 'sheet_id.state')
    def _compute_state(self):
        for expense in self:
            if not expense.sheet_id or expense.sheet_id.state == 'draft':
                expense.state = "draft"
            elif expense.sheet_id.state == "cancel":
                expense.state = "refused"
            elif expense.sheet_id.state == "approve" or expense.sheet_id.state == "post":
                expense.state = "approved"
            elif not expense.sheet_id.account_move_id:
                expense.state = "reported"
            else:
                expense.state = "done"

    @api.depends('quantity', 'unit_amount', 'tax_ids', 'currency_id')
    def _compute_amount(self):
        for expense in self:
            expense.untaxed_amount = expense.unit_amount * expense.quantity
            taxes = expense.tax_ids.compute_all(
                expense.unit_amount, expense.currency_id, expense.quantity,
                expense.product_id, expense.employee_id.user_id.partner_id)
            expense.total_amount = taxes.get('total_included')

    @api.depends('date', 'total_amount', 'company_currency_id')
    def _compute_total_amount_company(self):
        for expense in self:
            amount = 0
            if expense.company_currency_id:
                date_expense = expense.date
                amount = expense.currency_id._convert(
                    expense.total_amount, expense.company_currency_id,
                    expense.company_id or expense.sheet_id.company_id,
                    date_expense or fields.Date.today())
            expense.total_amount_company = amount

    @api.multi
    def _compute_attachment_number(self):
        attachment_data = self.env['ir.attachment'].read_group(
            [('res_model', '=', 'hr.expense'),
             ('res_id', 'in', self.ids)], ['res_id'], ['res_id'])
        attachment = dict(
            (data['res_id'], data['res_id_count']) for data in attachment_data)
        for expense in self:
            expense.attachment_number = attachment.get(expense.id, 0)

    @api.onchange('product_id')
    def _onchange_product_id(self):
        if self.product_id:
            if not self.name:
                self.name = self.product_id.display_name or ''
            self.unit_amount = self.product_id.price_compute('standard_price')[
                self.product_id.id]
            self.product_uom_id = self.product_id.uom_id
            self.tax_ids = self.product_id.supplier_taxes_id
            account = self.product_id.product_tmpl_id._get_product_accounts(
            )['expense']
            if account:
                self.account_id = account

    @api.onchange('product_uom_id')
    def _onchange_product_uom_id(self):
        if self.product_id and self.product_uom_id.category_id != self.product_id.uom_id.category_id:
            raise UserError(
                _('Selected Unit of Measure does not belong to the same category as the product Unit of Measure.'
                  ))

    # ----------------------------------------
    # ORM Overrides
    # ----------------------------------------

    @api.multi
    def unlink(self):
        for expense in self:
            if expense.state in ['done', 'approved']:
                raise UserError(
                    _('You cannot delete a posted or approved expense.'))
        return super(HrExpense, self).unlink()

    @api.model
    def get_empty_list_help(self, help_message):
        if help_message and "o_view_nocontent_smiling_face" not in help_message:
            use_mailgateway = self.env['ir.config_parameter'].sudo().get_param(
                'hr_expense.use_mailgateway')
            alias_record = use_mailgateway and self.env.ref(
                'hr_expense.mail_alias_expense') or False
            if alias_record and alias_record.alias_domain and alias_record.alias_name:
                link = "<a id='o_mail_test' href='mailto:%(email)s?subject=Lunch%%20with%%20customer%%3A%%20%%2412.32'>%(email)s</a>" % {
                    'email':
                    '%s@%s' %
                    (alias_record.alias_name, alias_record.alias_domain)
                }
                return '<p class="o_view_nocontent_smiling_face">%s</p><p class="oe_view_nocontent_alias">%s</p>' % (
                    _('Add a new expense,'),
                    _('or send receipts by email to %s.') % (link),
                )
        return super(HrExpense, self).get_empty_list_help(help_message)

    # ----------------------------------------
    # Actions
    # ----------------------------------------

    @api.multi
    def action_view_sheet(self):
        self.ensure_one()
        return {
            'type': 'ir.actions.act_window',
            'view_mode': 'form',
            'res_model': 'hr.expense.sheet',
            'target': 'current',
            'res_id': self.sheet_id.id
        }

    @api.multi
    def action_submit_expenses(self):
        if any(expense.state != 'draft' or expense.sheet_id
               for expense in self):
            raise UserError(_("You cannot report twice the same line!"))
        if len(self.mapped('employee_id')) != 1:
            raise UserError(
                _("You cannot report expenses for different employees in the same report."
                  ))

        todo = self.filtered(
            lambda x: x.payment_mode == 'own_account') or self.filtered(
                lambda x: x.payment_mode == 'company_account')
        return {
            'name': _('New Expense Report'),
            'type': 'ir.actions.act_window',
            'view_mode': 'form',
            'res_model': 'hr.expense.sheet',
            'target': 'current',
            'context': {
                'default_expense_line_ids': todo.ids,
                'default_employee_id': self[0].employee_id.id,
                'default_name': todo[0].name if len(todo) == 1 else ''
            }
        }

    @api.multi
    def action_get_attachment_view(self):
        self.ensure_one()
        res = self.env['ir.actions.act_window'].for_xml_id(
            'base', 'action_attachment')
        res['domain'] = [('res_model', '=', 'hr.expense'),
                         ('res_id', 'in', self.ids)]
        res['context'] = {
            'default_res_model': 'hr.expense',
            'default_res_id': self.id
        }
        return res

    # ----------------------------------------
    # Business
    # ----------------------------------------

    @api.multi
    def _prepare_move_values(self):
        """
        This function prepares move values related to an expense
        """
        self.ensure_one()
        journal = self.sheet_id.bank_journal_id if self.payment_mode == 'company_account' else self.sheet_id.journal_id
        account_date = self.sheet_id.accounting_date or self.date
        move_values = {
            'journal_id': journal.id,
            'company_id': self.env.user.company_id.id,
            'date': account_date,
            'ref': self.sheet_id.name,
            # force the name to the default value, to avoid an eventual 'default_name' in the context
            # to set it to '' which cause no number to be given to the account.move when posted.
            'name': '/',
        }
        return move_values

    @api.multi
    def _get_account_move_by_sheet(self):
        """ Return a mapping between the expense sheet of current expense and its account move
            :returns dict where key is a sheet id, and value is an account move record
        """
        move_grouped_by_sheet = {}
        for expense in self:
            # create the move that will contain the accounting entries
            if expense.sheet_id.id not in move_grouped_by_sheet:
                move = self.env['account.move'].create(
                    expense._prepare_move_values())
                move_grouped_by_sheet[expense.sheet_id.id] = move
            else:
                move = move_grouped_by_sheet[expense.sheet_id.id]
        return move_grouped_by_sheet

    @api.multi
    def _get_expense_account_source(self):
        self.ensure_one()
        if self.account_id:
            account = self.account_id
        elif self.product_id:
            account = self.product_id.product_tmpl_id._get_product_accounts(
            )['expense']
            if not account:
                raise UserError(
                    _("No Expense account found for the product %s (or for its category), please configure one."
                      ) % (self.product_id.name))
        else:
            account = self.env['ir.property'].with_context(
                force_company=self.company_id.id).get(
                    'property_account_expense_categ_id', 'product.category')
            if not account:
                raise UserError(
                    _('Please configure Default Expense account for Product expense: `property_account_expense_categ_id`.'
                      ))
        return account

    @api.multi
    def _get_expense_account_destination(self):
        self.ensure_one()
        account_dest = self.env['account.account']
        if self.payment_mode == 'company_account':
            if not self.sheet_id.bank_journal_id.default_credit_account_id:
                raise UserError(
                    _("No credit account found for the %s journal, please configure one."
                      ) % (self.sheet_id.bank_journal_id.name))
            account_dest = self.sheet_id.bank_journal_id.default_credit_account_id.id
        else:
            if not self.employee_id.address_home_id:
                raise UserError(
                    _("No Home Address found for the employee %s, please configure one."
                      ) % (self.employee_id.name))
            account_dest = self.employee_id.address_home_id.property_account_payable_id.id
        return account_dest

    @api.multi
    def _get_account_move_line_values(self):
        move_line_values_by_expense = {}
        for expense in self:
            move_line_name = expense.employee_id.name + ': ' + expense.name.split(
                '\n')[0][:64]
            account_src = expense._get_expense_account_source()
            account_dst = expense._get_expense_account_destination()
            account_date = expense.sheet_id.accounting_date or expense.date or fields.Date.context_today(
                expense)

            company_currency = expense.company_id.currency_id
            different_currency = expense.currency_id and expense.currency_id != company_currency

            move_line_values = []
            taxes = expense.tax_ids.with_context(round=True).compute_all(
                expense.unit_amount, expense.currency_id, expense.quantity,
                expense.product_id)
            total_amount = 0.0
            total_amount_currency = 0.0
            partner_id = expense.employee_id.address_home_id.commercial_partner_id.id

            # source move line
            amount = taxes['total_excluded']
            amount_currency = False
            if different_currency:
                amount = expense.currency_id._convert(amount, company_currency,
                                                      expense.company_id,
                                                      account_date)
                amount_currency = taxes['total_excluded']
            move_line_src = {
                'name':
                move_line_name,
                'quantity':
                expense.quantity or 1,
                'debit':
                amount if amount > 0 else 0,
                'credit':
                -amount if amount < 0 else 0,
                'amount_currency':
                amount_currency if different_currency else 0.0,
                'account_id':
                account_src.id,
                'product_id':
                expense.product_id.id,
                'product_uom_id':
                expense.product_uom_id.id,
                'analytic_account_id':
                expense.analytic_account_id.id,
                'analytic_tag_ids': [(6, 0, expense.analytic_tag_ids.ids)],
                'expense_id':
                expense.id,
                'partner_id':
                partner_id,
                'tax_ids': [(6, 0, expense.tax_ids.ids)],
                'currency_id':
                expense.currency_id.id if different_currency else False,
            }
            move_line_values.append(move_line_src)
            total_amount += -move_line_src['debit'] or move_line_src['credit']
            total_amount_currency += -move_line_src[
                'amount_currency'] if move_line_src['currency_id'] else (
                    -move_line_src['debit'] or move_line_src['credit'])

            # taxes move lines
            for tax in taxes['taxes']:
                amount = tax['amount']
                amount_currency = False
                if different_currency:
                    amount = expense.currency_id._convert(
                        amount, company_currency, expense.company_id,
                        account_date)
                    amount_currency = tax['amount']
                move_line_tax_values = {
                    'name':
                    tax['name'],
                    'quantity':
                    1,
                    'debit':
                    amount if amount > 0 else 0,
                    'credit':
                    -amount if amount < 0 else 0,
                    'amount_currency':
                    amount_currency if different_currency else 0.0,
                    'account_id':
                    tax['account_id'] or move_line_src['account_id'],
                    'tax_line_id':
                    tax['id'],
                    'expense_id':
                    expense.id,
                    'partner_id':
                    partner_id,
                    'currency_id':
                    expense.currency_id.id if different_currency else False,
                    'analytic_account_id':
                    expense.analytic_account_id.id
                    if tax['analytic'] else False,
                    'analytic_tag_ids': [(6, 0, expense.analytic_tag_ids.ids)]
                    if tax['analytic'] else False,
                }
                total_amount -= amount
                total_amount_currency -= move_line_tax_values[
                    'amount_currency'] or amount
                move_line_values.append(move_line_tax_values)

            # destination move line
            move_line_dst = {
                'name':
                move_line_name,
                'debit':
                total_amount > 0 and total_amount,
                'credit':
                total_amount < 0 and -total_amount,
                'account_id':
                account_dst,
                'date_maturity':
                account_date,
                'amount_currency':
                total_amount_currency if different_currency else 0.0,
                'currency_id':
                expense.currency_id.id if different_currency else False,
                'expense_id':
                expense.id,
                'partner_id':
                partner_id,
            }
            move_line_values.append(move_line_dst)

            move_line_values_by_expense[expense.id] = move_line_values
        return move_line_values_by_expense

    @api.multi
    def action_move_create(self):
        '''
        main function that is called when trying to create the accounting entries related to an expense
        '''
        move_group_by_sheet = self._get_account_move_by_sheet()

        move_line_values_by_expense = self._get_account_move_line_values()

        for expense in self:
            company_currency = expense.company_id.currency_id
            different_currency = expense.currency_id != company_currency

            # get the account move of the related sheet
            move = move_group_by_sheet[expense.sheet_id.id]

            # get move line values
            move_line_values = move_line_values_by_expense.get(expense.id)
            move_line_dst = move_line_values[-1]
            total_amount = move_line_dst['debit'] or -move_line_dst['credit']
            total_amount_currency = move_line_dst['amount_currency']

            # create one more move line, a counterline for the total on payable account
            if expense.payment_mode == 'company_account':
                if not expense.sheet_id.bank_journal_id.default_credit_account_id:
                    raise UserError(
                        _("No credit account found for the %s journal, please configure one."
                          ) % (expense.sheet_id.bank_journal_id.name))
                journal = expense.sheet_id.bank_journal_id
                # create payment
                payment_methods = journal.outbound_payment_method_ids if total_amount < 0 else journal.inbound_payment_method_ids
                journal_currency = journal.currency_id or journal.company_id.currency_id
                payment = self.env['account.payment'].create({
                    'payment_method_id':
                    payment_methods and payment_methods[0].id or False,
                    'payment_type':
                    'outbound' if total_amount < 0 else 'inbound',
                    'partner_id':
                    expense.employee_id.address_home_id.commercial_partner_id.
                    id,
                    'partner_type':
                    'supplier',
                    'journal_id':
                    journal.id,
                    'payment_date':
                    expense.date,
                    'state':
                    'reconciled',
                    'currency_id':
                    expense.currency_id.id
                    if different_currency else journal_currency.id,
                    'amount':
                    abs(total_amount_currency)
                    if different_currency else abs(total_amount),
                    'name':
                    expense.name,
                })
                move_line_dst['payment_id'] = payment.id

            # link move lines to move, and move to expense sheet
            move.with_context(dont_create_taxes=True).write(
                {'line_ids': [(0, 0, line) for line in move_line_values]})
            expense.sheet_id.write({'account_move_id': move.id})

            if expense.payment_mode == 'company_account':
                expense.sheet_id.paid_expense_sheets()

        # post the moves
        for move in move_group_by_sheet.values():
            move.post()

        return move_group_by_sheet

    @api.multi
    def refuse_expense(self, reason):
        self.write({'is_refused': True})
        self.sheet_id.write({'state': 'cancel'})
        self.sheet_id.message_post_with_view(
            'hr_expense.hr_expense_template_refuse_reason',
            values={
                'reason': reason,
                'is_sheet': False,
                'name': self.name
            })

    # ----------------------------------------
    # Mail Thread
    # ----------------------------------------

    @api.model
    def message_new(self, msg_dict, custom_values=None):
        if custom_values is None:
            custom_values = {}

        email_address = email_split(msg_dict.get('email_from', False))[0]

        employee = self.env['hr.employee'].search([
            '|', ('work_email', 'ilike', email_address),
            ('user_id.email', 'ilike', email_address)
        ],
                                                  limit=1)
        # The expenses alias is the same for all companies, we need to set the proper context
        company = employee.company_id or self.env.user.company_id
        self = self.with_context(force_company=company.id)

        expense_description = msg_dict.get('subject', '')

        # Match the first occurence of '[]' in the string and extract the content inside it
        # Example: '[foo] bar (baz)' becomes 'foo'. This is potentially the product code
        # of the product to encode on the expense. If not, take the default product instead
        # which is 'Fixed Cost'
        default_product = self.env.ref('hr_expense.product_product_fixed_cost')
        pattern = '\[([^)]*)\]'
        product_code = re.search(pattern, expense_description)
        if product_code is None:
            product = default_product
        else:
            expense_description = expense_description.replace(
                product_code.group(), '')
            products = self.env['product.product'].search([
                ('default_code', 'ilike', product_code.group(1))
            ]) or default_product
            product = products.filtered(lambda p: p.default_code ==
                                        product_code.group(1)) or products[0]
        account = product.product_tmpl_id._get_product_accounts()['expense']

        pattern = '[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?'
        # Match the last occurence of a float in the string
        # Example: '[foo] 50.3 bar 34.5' becomes '34.5'. This is potentially the price
        # to encode on the expense. If not, take 1.0 instead
        expense_price = re.findall(pattern, expense_description)
        # TODO: International formatting
        if not expense_price:
            price = 1.0
        else:
            price = expense_price[-1][0]
            expense_description = expense_description.replace(price, '')
            try:
                price = float(price)
            except ValueError:
                price = 1.0

        custom_values.update({
            'name':
            expense_description.strip(),
            'employee_id':
            employee.id,
            'product_id':
            product.id,
            'product_uom_id':
            product.uom_id.id,
            'tax_ids':
            [(4, tax.id, False) for tax in product.supplier_taxes_id],
            'quantity':
            1,
            'unit_amount':
            price,
            'company_id':
            employee.company_id.id,
            'currency_id':
            employee.company_id.currency_id.id,
        })
        if account:
            custom_values['account_id'] = account.id
        return super(HrExpense, self).message_new(msg_dict, custom_values)
예제 #26
0
파일: purchase.py 프로젝트: metricsw/swerp
class PurchaseOrderLine(models.Model):
    _name = 'purchase.order.line'
    _description = 'Purchase Order Line'
    _order = 'order_id, sequence, id'

    name = fields.Text(string='Description', required=True)
    sequence = fields.Integer(string='Sequence', default=10)
    product_qty = fields.Float(
        string='Quantity',
        digits=dp.get_precision('Product Unit of Measure'),
        required=True)
    product_uom_qty = fields.Float(string='Total Quantity',
                                   compute='_compute_product_uom_qty',
                                   store=True)
    date_planned = fields.Datetime(string='Scheduled Date',
                                   required=True,
                                   index=True)
    taxes_id = fields.Many2many(
        'account.tax',
        string='Taxes',
        domain=['|', ('active', '=', False), ('active', '=', True)])
    product_uom = fields.Many2one('uom.uom',
                                  string='Product Unit of Measure',
                                  required=True)
    product_id = fields.Many2one('product.product',
                                 string='Product',
                                 domain=[('purchase_ok', '=', True)],
                                 change_default=True,
                                 required=True)
    product_image = fields.Binary(
        'Product Image',
        related="product_id.image",
        readonly=False,
        help=
        "Non-stored related field to allow portal user to see the image of the product he has ordered"
    )
    product_type = fields.Selection(related='product_id.type', readonly=True)
    price_unit = fields.Float(string='Unit Price',
                              required=True,
                              digits=dp.get_precision('Product Price'))

    price_subtotal = fields.Monetary(compute='_compute_amount',
                                     string='Subtotal',
                                     store=True)
    price_total = fields.Monetary(compute='_compute_amount',
                                  string='Total',
                                  store=True)
    price_tax = fields.Float(compute='_compute_amount',
                             string='Tax',
                             store=True)

    order_id = fields.Many2one('purchase.order',
                               string='Order Reference',
                               index=True,
                               required=True,
                               ondelete='cascade')
    account_analytic_id = fields.Many2one('account.analytic.account',
                                          string='Analytic Account')
    analytic_tag_ids = fields.Many2many('account.analytic.tag',
                                        string='Analytic Tags')
    company_id = fields.Many2one('res.company',
                                 related='order_id.company_id',
                                 string='Company',
                                 store=True,
                                 readonly=True)
    state = fields.Selection(related='order_id.state',
                             store=True,
                             readonly=False)

    invoice_lines = fields.One2many('account.invoice.line',
                                    'purchase_line_id',
                                    string="Bill Lines",
                                    readonly=True,
                                    copy=False)

    # Replace by invoiced Qty
    qty_invoiced = fields.Float(
        compute='_compute_qty_invoiced',
        string="Billed Qty",
        digits=dp.get_precision('Product Unit of Measure'),
        store=True)
    qty_received = fields.Float(
        string="Received Qty",
        digits=dp.get_precision('Product Unit of Measure'),
        copy=False)

    partner_id = fields.Many2one('res.partner',
                                 related='order_id.partner_id',
                                 string='Partner',
                                 readonly=True,
                                 store=True)
    currency_id = fields.Many2one(related='order_id.currency_id',
                                  store=True,
                                  string='Currency',
                                  readonly=True)
    date_order = fields.Datetime(related='order_id.date_order',
                                 string='Order Date',
                                 readonly=True)

    @api.depends('product_qty', 'price_unit', 'taxes_id')
    def _compute_amount(self):
        for line in self:
            vals = line._prepare_compute_all_values()
            taxes = line.taxes_id.compute_all(vals['price_unit'],
                                              vals['currency_id'],
                                              vals['product_qty'],
                                              vals['product'], vals['partner'])
            line.update({
                'price_tax':
                sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])),
                'price_total':
                taxes['total_included'],
                'price_subtotal':
                taxes['total_excluded'],
            })

    def _prepare_compute_all_values(self):
        # Hook method to returns the different argument values for the
        # compute_all method, due to the fact that discounts mechanism
        # is not implemented yet on the purchase orders.
        # This method should disappear as soon as this feature is
        # also introduced like in the sales module.
        self.ensure_one()
        return {
            'price_unit': self.price_unit,
            'currency_id': self.order_id.currency_id,
            'product_qty': self.product_qty,
            'product': self.product_id,
            'partner': self.order_id.partner_id,
        }

    @api.multi
    def _compute_tax_id(self):
        for line in self:
            fpos = line.order_id.fiscal_position_id or line.order_id.partner_id.property_account_position_id
            # If company_id is set, always filter taxes by the company
            taxes = line.product_id.supplier_taxes_id.filtered(
                lambda r: not line.company_id or r.company_id == line.
                company_id)
            line.taxes_id = fpos.map_tax(
                taxes, line.product_id,
                line.order_id.partner_id) if fpos else taxes

    @api.depends('invoice_lines.invoice_id.state', 'invoice_lines.quantity')
    def _compute_qty_invoiced(self):
        for line in self:
            qty = 0.0
            for inv_line in line.invoice_lines:
                if inv_line.invoice_id.state not in ['cancel']:
                    if inv_line.invoice_id.type == 'in_invoice':
                        qty += inv_line.uom_id._compute_quantity(
                            inv_line.quantity, line.product_uom)
                    elif inv_line.invoice_id.type == 'in_refund':
                        qty -= inv_line.uom_id._compute_quantity(
                            inv_line.quantity, line.product_uom)
            line.qty_invoiced = qty

    @api.model
    def create(self, values):
        line = super(PurchaseOrderLine, self).create(values)
        if line.order_id.state == 'purchase':
            msg = _("Extra line with %s ") % (line.product_id.display_name, )
            line.order_id.message_post(body=msg)
        return line

    @api.multi
    def write(self, values):
        if 'product_qty' in values:
            for line in self:
                if line.order_id.state == 'purchase':
                    line.order_id.message_post_with_view(
                        'purchase.track_po_line_template',
                        values={
                            'line': line,
                            'product_qty': values['product_qty']
                        },
                        subtype_id=self.env.ref('mail.mt_note').id)
        return super(PurchaseOrderLine, self).write(values)

    @api.multi
    def unlink(self):
        for line in self:
            if line.order_id.state in ['purchase', 'done']:
                raise UserError(
                    _('Cannot delete a purchase order line which is in state \'%s\'.'
                      ) % (line.state, ))
        return super(PurchaseOrderLine, self).unlink()

    @api.model
    def _get_date_planned(self, seller, po=False):
        """Return the datetime value to use as Schedule Date (``date_planned``) for
           PO Lines that correspond to the given product.seller_ids,
           when ordered at `date_order_str`.

           :param Model seller: used to fetch the delivery delay (if no seller
                                is provided, the delay is 0)
           :param Model po: purchase.order, necessary only if the PO line is
                            not yet attached to a PO.
           :rtype: datetime
           :return: desired Schedule Date for the PO line
        """
        date_order = po.date_order if po else self.order_id.date_order
        if date_order:
            return date_order + relativedelta(
                days=seller.delay if seller else 0)
        else:
            return datetime.today() + relativedelta(
                days=seller.delay if seller else 0)

    @api.onchange('product_id')
    def onchange_product_id(self):
        result = {}
        if not self.product_id:
            return result

        # Reset date, price and quantity since _onchange_quantity will provide default values
        self.date_planned = datetime.today().strftime(
            DEFAULT_SERVER_DATETIME_FORMAT)
        self.price_unit = self.product_qty = 0.0
        self.product_uom = self.product_id.uom_po_id or self.product_id.uom_id
        result['domain'] = {
            'product_uom':
            [('category_id', '=', self.product_id.uom_id.category_id.id)]
        }

        product_lang = self.product_id.with_context(
            lang=self.partner_id.lang,
            partner_id=self.partner_id.id,
        )
        self.name = product_lang.display_name
        if product_lang.description_purchase:
            self.name += '\n' + product_lang.description_purchase

        self._compute_tax_id()

        self._suggest_quantity()
        self._onchange_quantity()

        return result

    @api.onchange('product_id')
    def onchange_product_id_warning(self):
        if not self.product_id:
            return
        warning = {}
        title = False
        message = False

        product_info = self.product_id

        if product_info.purchase_line_warn != 'no-message':
            title = _("Warning for %s") % product_info.name
            message = product_info.purchase_line_warn_msg
            warning['title'] = title
            warning['message'] = message
            if product_info.purchase_line_warn == 'block':
                self.product_id = False
            return {'warning': warning}
        return {}

    @api.onchange('product_qty', 'product_uom')
    def _onchange_quantity(self):
        if not self.product_id:
            return
        params = {'order_id': self.order_id}
        seller = self.product_id._select_seller(
            partner_id=self.partner_id,
            quantity=self.product_qty,
            date=self.order_id.date_order and self.order_id.date_order.date(),
            uom_id=self.product_uom,
            params=params)

        if seller or not self.date_planned:
            self.date_planned = self._get_date_planned(seller).strftime(
                DEFAULT_SERVER_DATETIME_FORMAT)

        if not seller:
            if self.product_id.seller_ids.filtered(
                    lambda s: s.name.id == self.partner_id.id):
                self.price_unit = 0.0
            return

        price_unit = self.env['account.tax']._fix_tax_included_price_company(
            seller.price, self.product_id.supplier_taxes_id, self.taxes_id,
            self.company_id) if seller else 0.0
        if price_unit and seller and self.order_id.currency_id and seller.currency_id != self.order_id.currency_id:
            price_unit = seller.currency_id._convert(
                price_unit, self.order_id.currency_id,
                self.order_id.company_id, self.date_order
                or fields.Date.today())

        if seller and self.product_uom and seller.product_uom != self.product_uom:
            price_unit = seller.product_uom._compute_price(
                price_unit, self.product_uom)

        self.price_unit = price_unit

    @api.multi
    @api.depends('product_uom', 'product_qty', 'product_id.uom_id')
    def _compute_product_uom_qty(self):
        for line in self:
            if line.product_id.uom_id != line.product_uom:
                line.product_uom_qty = line.product_uom._compute_quantity(
                    line.product_qty, line.product_id.uom_id)
            else:
                line.product_uom_qty = line.product_qty

    def _suggest_quantity(self):
        '''
        Suggest a minimal quantity based on the seller
        '''
        if not self.product_id:
            return
        seller_min_qty = self.product_id.seller_ids\
            .filtered(lambda r: r.name == self.order_id.partner_id and (not r.product_id or r.product_id == self.product_id))\
            .sorted(key=lambda r: r.min_qty)
        if seller_min_qty:
            self.product_qty = seller_min_qty[0].min_qty or 1.0
            self.product_uom = seller_min_qty[0].product_uom
        else:
            self.product_qty = 1.0
예제 #27
0
class HrExpenseSheet(models.Model):
    """
        Here are the rights associated with the expense flow

        Action       Group                   Restriction
        =================================================================================
        Submit      Employee                Only his own
                    Officer                 If he is expense manager of the employee, manager of the employee
                                             or the employee is in the department managed by the officer
                    Manager                 Always
        Approve     Officer                 Not his own and he is expense manager of the employee, manager of the employee
                                             or the employee is in the department managed by the officer
                    Manager                 Always
        Post        Anybody                 State = approve and journal_id defined
        Done        Anybody                 State = approve and journal_id defined
        Cancel      Officer                 Not his own and he is expense manager of the employee, manager of the employee
                                             or the employee is in the department managed by the officer
                    Manager                 Always
        =================================================================================
    """
    _name = "hr.expense.sheet"
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _description = "Expense Report"
    _order = "accounting_date desc, id desc"

    @api.model
    def _default_journal_id(self):
        journal = self.env.ref('hr_expense.hr_expense_account_journal',
                               raise_if_not_found=False)
        if not journal:
            journal = self.env['account.journal'].search(
                [('type', '=', 'purchase')], limit=1)
        return journal.id

    @api.model
    def _default_bank_journal_id(self):
        return self.env['account.journal'].search(
            [('type', 'in', ['cash', 'bank'])], limit=1)

    name = fields.Char('Expense Report Summary', required=True)
    expense_line_ids = fields.One2many('hr.expense',
                                       'sheet_id',
                                       string='Expense Lines',
                                       states={
                                           'approve': [('readonly', True)],
                                           'done': [('readonly', True)],
                                           'post': [('readonly', True)]
                                       },
                                       copy=False)
    state = fields.Selection([('draft', 'Draft'), ('submit', 'Submitted'),
                              ('approve', 'Approved'), ('post', 'Posted'),
                              ('done', 'Paid'), ('cancel', 'Refused')],
                             string='Status',
                             index=True,
                             readonly=True,
                             track_visibility='onchange',
                             copy=False,
                             default='draft',
                             required=True,
                             help='Expense Report State')
    employee_id = fields.Many2one(
        'hr.employee',
        string="Employee",
        required=True,
        readonly=True,
        states={'draft': [('readonly', False)]},
        default=lambda self: self.env['hr.employee'].search(
            [('user_id', '=', self.env.uid)], limit=1))
    address_id = fields.Many2one('res.partner', string="Employee Home Address")
    payment_mode = fields.Selection(
        [("own_account", "Employee (to reimburse)"),
         ("company_account", "Company")],
        related='expense_line_ids.payment_mode',
        default='own_account',
        readonly=True,
        string="Paid By")
    user_id = fields.Many2one('res.users',
                              'Manager',
                              readonly=True,
                              copy=False,
                              states={'draft': [('readonly', False)]},
                              track_visibility='onchange',
                              oldname='responsible_id')
    total_amount = fields.Monetary('Total Amount',
                                   currency_field='currency_id',
                                   compute='_compute_amount',
                                   store=True,
                                   digits=dp.get_precision('Account'))
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 readonly=True,
                                 states={'draft': [('readonly', False)]},
                                 default=lambda self: self.env.user.company_id)
    currency_id = fields.Many2one(
        'res.currency',
        string='Currency',
        readonly=True,
        states={'draft': [('readonly', False)]},
        default=lambda self: self.env.user.company_id.currency_id)
    attachment_number = fields.Integer(compute='_compute_attachment_number',
                                       string='Number of Attachments')
    journal_id = fields.Many2one(
        'account.journal',
        string='Expense Journal',
        states={
            'done': [('readonly', True)],
            'post': [('readonly', True)]
        },
        default=_default_journal_id,
        help="The journal used when the expense is done.")
    bank_journal_id = fields.Many2one(
        'account.journal',
        string='Bank Journal',
        states={
            'done': [('readonly', True)],
            'post': [('readonly', True)]
        },
        default=_default_bank_journal_id,
        help="The payment method used when the expense is paid by the company."
    )
    accounting_date = fields.Date("Date")
    account_move_id = fields.Many2one('account.move',
                                      string='Journal Entry',
                                      ondelete='restrict',
                                      copy=False)
    department_id = fields.Many2one('hr.department',
                                    string='Department',
                                    states={
                                        'post': [('readonly', True)],
                                        'done': [('readonly', True)]
                                    })
    is_multiple_currency = fields.Boolean(
        "Handle lines with different currencies",
        compute='_compute_is_multiple_currency')
    can_reset = fields.Boolean('Can Reset', compute='_compute_can_reset')

    @api.depends('expense_line_ids.total_amount_company')
    def _compute_amount(self):
        for sheet in self:
            sheet.total_amount = sum(
                sheet.expense_line_ids.mapped('total_amount_company'))

    @api.multi
    def _compute_attachment_number(self):
        for sheet in self:
            sheet.attachment_number = sum(
                sheet.expense_line_ids.mapped('attachment_number'))

    @api.depends('expense_line_ids.currency_id')
    def _compute_is_multiple_currency(self):
        for sheet in self:
            sheet.is_multiple_currency = len(
                sheet.expense_line_ids.mapped('currency_id')) > 1

    @api.multi
    def _compute_can_reset(self):
        is_expense_user = self.user_has_groups(
            'hr_expense.group_hr_expense_user')
        for sheet in self:
            sheet.can_reset = is_expense_user if is_expense_user else sheet.employee_id.user_id == self.env.user

    @api.onchange('employee_id')
    def _onchange_employee_id(self):
        self.address_id = self.employee_id.sudo().address_home_id
        self.department_id = self.employee_id.department_id
        self.user_id = self.employee_id.expense_manager_id or self.employee_id.parent_id.user_id

    @api.multi
    @api.constrains('expense_line_ids')
    def _check_payment_mode(self):
        for sheet in self:
            expense_lines = sheet.mapped('expense_line_ids')
            if expense_lines and any(
                    expense.payment_mode != expense_lines[0].payment_mode
                    for expense in expense_lines):
                raise ValidationError(
                    _("Expenses must be paid by the same entity (Company or employee)."
                      ))

    @api.constrains('expense_line_ids', 'employee_id')
    def _check_employee(self):
        for sheet in self:
            employee_ids = sheet.expense_line_ids.mapped('employee_id')
            if len(employee_ids) > 1 or (len(employee_ids) == 1 and
                                         employee_ids != sheet.employee_id):
                raise ValidationError(
                    _('You cannot add expenses of another employee.'))

    @api.model
    def create(self, vals):
        sheet = super(
            HrExpenseSheet,
            self.with_context(mail_create_nosubscribe=True)).create(vals)
        sheet.activity_update()
        return sheet

    @api.multi
    def unlink(self):
        for expense in self:
            if expense.state in ['post', 'done']:
                raise UserError(
                    _('You cannot delete a posted or paid expense.'))
        super(HrExpenseSheet, self).unlink()

    # --------------------------------------------
    # Mail Thread
    # --------------------------------------------

    @api.multi
    def _track_subtype(self, init_values):
        self.ensure_one()
        if 'state' in init_values and self.state == 'approve':
            return 'hr_expense.mt_expense_approved'
        elif 'state' in init_values and self.state == 'cancel':
            return 'hr_expense.mt_expense_refused'
        elif 'state' in init_values and self.state == 'done':
            return 'hr_expense.mt_expense_paid'
        return super(HrExpenseSheet, self)._track_subtype(init_values)

    def _message_auto_subscribe_followers(self, updated_values, subtype_ids):
        res = super(HrExpenseSheet, self)._message_auto_subscribe_followers(
            updated_values, subtype_ids)
        if updated_values.get('employee_id'):
            employee = self.env['hr.employee'].browse(
                updated_values['employee_id'])
            if employee.user_id:
                res.append(
                    (employee.user_id.partner_id.id, subtype_ids, False))
        return res

    # --------------------------------------------
    # Actions
    # --------------------------------------------

    @api.multi
    def action_sheet_move_create(self):
        if any(sheet.state != 'approve' for sheet in self):
            raise UserError(
                _("You can only generate accounting entry for approved expense(s)."
                  ))

        if any(not sheet.journal_id for sheet in self):
            raise UserError(
                _("Expenses must have an expense journal specified to generate accounting entries."
                  ))

        expense_line_ids = self.mapped('expense_line_ids')\
            .filtered(lambda r: not float_is_zero(r.total_amount, precision_rounding=(r.currency_id or self.env.user.company_id.currency_id).rounding))
        res = expense_line_ids.action_move_create()

        if not self.accounting_date:
            self.accounting_date = self.account_move_id.date

        if self.payment_mode == 'own_account' and expense_line_ids:
            self.write({'state': 'post'})
        else:
            self.write({'state': 'done'})
        self.activity_update()
        return res

    @api.multi
    def action_get_attachment_view(self):
        res = self.env['ir.actions.act_window'].for_xml_id(
            'base', 'action_attachment')
        res['domain'] = [('res_model', '=', 'hr.expense'),
                         ('res_id', 'in', self.expense_line_ids.ids)]
        res['context'] = {
            'default_res_model': 'hr.expense.sheet',
            'default_res_id': self.id,
            'create': False,
            'edit': False,
        }
        return res

    # --------------------------------------------
    # Business
    # --------------------------------------------

    @api.multi
    def set_to_paid(self):
        self.write({'state': 'done'})

    @api.multi
    def action_submit_sheet(self):
        self.write({'state': 'submit'})
        self.activity_update()

    @api.multi
    def approve_expense_sheets(self):
        if not self.user_has_groups('hr_expense.group_hr_expense_user'):
            raise UserError(
                _("Only Managers and HR Officers can approve expenses"))
        elif not self.user_has_groups('hr_expense.group_hr_expense_manager'):
            current_managers = self.employee_id.parent_id.user_id | self.employee_id.department_id.manager_id.user_id | self.employee_id.expense_manager_id

            if self.employee_id.user_id == self.env.user:
                raise UserError(_("You cannot approve your own expenses"))

            if not self.env.user in current_managers:
                raise UserError(
                    _("You can only approve your department expenses"))

        responsible_id = self.user_id.id or self.env.user.id
        self.write({'state': 'approve', 'user_id': responsible_id})
        self.activity_update()

    @api.multi
    def paid_expense_sheets(self):
        self.write({'state': 'done'})

    @api.multi
    def refuse_sheet(self, reason):
        if not self.user_has_groups('hr_expense.group_hr_expense_user'):
            raise UserError(
                _("Only Managers and HR Officers can approve expenses"))
        elif not self.user_has_groups('hr_expense.group_hr_expense_manager'):
            current_managers = self.employee_id.parent_id.user_id | self.employee_id.department_id.manager_id.user_id | self.employee_id.expense_manager_id

            if self.employee_id.user_id == self.env.user:
                raise UserError(_("You cannot refuse your own expenses"))

            if not self.env.user in current_managers:
                raise UserError(
                    _("You can only refuse your department expenses"))

        self.write({'state': 'cancel'})
        for sheet in self:
            sheet.message_post_with_view(
                'hr_expense.hr_expense_template_refuse_reason',
                values={
                    'reason': reason,
                    'is_sheet': True,
                    'name': self.name
                })
        self.activity_update()

    @api.multi
    def reset_expense_sheets(self):
        if not self.can_reset:
            raise UserError(
                _("Only HR Officers or the concerned employee can reset to draft."
                  ))
        self.mapped('expense_line_ids').write({'is_refused': False})
        self.write({'state': 'draft'})
        self.activity_update()
        return True

    def _get_responsible_for_approval(self):
        if self.user_id:
            return self.user_id
        elif self.employee_id.parent_id.user_id:
            return self.employee_id.parent_id.user_id
        elif self.employee_id.department_id.manager_id.user_id:
            return self.employee_id.department_id.manager_id.user_id
        return self.env['res.users']

    def activity_update(self):
        for expense_report in self.filtered(lambda hol: hol.state == 'submit'):
            self.activity_schedule('hr_expense.mail_act_expense_approval',
                                   user_id=expense_report.sudo(
                                   )._get_responsible_for_approval().id
                                   or self.env.user.id)
        self.filtered(lambda hol: hol.state == 'approve').activity_feedback(
            ['hr_expense.mail_act_expense_approval'])
        self.filtered(lambda hol: hol.state == 'cancel').activity_unlink(
            ['hr_expense.mail_act_expense_approval'])
예제 #28
0
class PosSession(models.Model):
    _name = 'pos.session'
    _order = 'id desc'
    _description = 'Point of Sale Session'

    POS_SESSION_STATE = [
        ('opening_control',
         'Opening Control'),  # method action_pos_session_open
        ('opened', 'In Progress'),  # method action_pos_session_closing_control
        ('closing_control',
         'Closing Control'),  # method action_pos_session_close
        ('closed', 'Closed & Posted'),
    ]

    def _confirm_orders(self):
        for session in self:
            company_id = session.config_id.journal_id.company_id.id
            orders = session.order_ids.filtered(
                lambda order: order.state == 'paid')
            journal_id = self.env['ir.config_parameter'].sudo().get_param(
                'pos.closing.journal_id_%s' % company_id,
                default=session.config_id.journal_id.id)
            if not journal_id:
                raise UserError(
                    _("You have to set a Sale Journal for the POS:%s") %
                    (session.config_id.name, ))

            move = self.env['pos.order'].with_context(
                force_company=company_id)._create_account_move(
                    session.start_at, session.name, int(journal_id),
                    company_id)
            orders.with_context(
                force_company=company_id)._create_account_move_line(
                    session, move)
            for order in session.order_ids.filtered(
                    lambda o: o.state not in ['done', 'invoiced']):
                if order.state not in ('paid'):
                    raise UserError(
                        _("You cannot confirm all orders of this session, because they have not the 'paid' status.\n"
                          "{reference} is in state {state}, total amount: {total}, paid: {paid}"
                          ).format(
                              reference=order.pos_reference or order.name,
                              state=dict(order._fields['state'].
                                         _description_selection(self.env)).get(
                                             order.state),
                              total=order.amount_total,
                              paid=order.amount_paid,
                          ))
                order.action_pos_order_done()
            orders_to_reconcile = session.order_ids._filtered_for_reconciliation(
            )
            orders_to_reconcile.sudo()._reconcile_payments()

    config_id = fields.Many2one(
        'pos.config',
        string='Point of Sale',
        help="The physical point of sale you will use.",
        required=True,
        index=True)
    name = fields.Char(string='Session ID',
                       required=True,
                       readonly=True,
                       default='/')
    user_id = fields.Many2one(
        'res.users',
        string='Responsible',
        required=True,
        index=True,
        readonly=True,
        states={'opening_control': [('readonly', False)]},
        default=lambda self: self.env.uid)
    currency_id = fields.Many2one('res.currency',
                                  related='config_id.currency_id',
                                  string="Currency",
                                  readonly=False)
    start_at = fields.Datetime(string='Opening Date', readonly=True)
    stop_at = fields.Datetime(string='Closing Date', readonly=True, copy=False)

    state = fields.Selection(POS_SESSION_STATE,
                             string='Status',
                             required=True,
                             readonly=True,
                             index=True,
                             copy=False,
                             default='opening_control')

    sequence_number = fields.Integer(
        string='Order Sequence Number',
        help='A sequence number that is incremented with each order',
        default=1)
    login_number = fields.Integer(
        string='Login Sequence Number',
        help=
        'A sequence number that is incremented each time a user resumes the pos session',
        default=0)

    cash_control = fields.Boolean(compute='_compute_cash_all',
                                  string='Has Cash Control')
    cash_journal_id = fields.Many2one('account.journal',
                                      compute='_compute_cash_all',
                                      string='Cash Journal',
                                      store=True)
    cash_register_id = fields.Many2one('account.bank.statement',
                                       compute='_compute_cash_all',
                                       string='Cash Register',
                                       store=True)

    cash_register_balance_end_real = fields.Monetary(
        related='cash_register_id.balance_end_real',
        string="Ending Balance",
        help="Total of closing cash control lines.",
        readonly=True)
    cash_register_balance_start = fields.Monetary(
        related='cash_register_id.balance_start',
        string="Starting Balance",
        help="Total of opening cash control lines.",
        readonly=True)
    cash_register_total_entry_encoding = fields.Monetary(
        related='cash_register_id.total_entry_encoding',
        string='Total Cash Transaction',
        readonly=True,
        help="Total of all paid sales orders")
    cash_register_balance_end = fields.Monetary(
        related='cash_register_id.balance_end',
        digits=0,
        string="Theoretical Closing Balance",
        help="Sum of opening balance and transactions.",
        readonly=True)
    cash_register_difference = fields.Monetary(
        related='cash_register_id.difference',
        string='Difference',
        help=
        "Difference between the theoretical closing balance and the real closing balance.",
        readonly=True)

    journal_ids = fields.Many2many('account.journal',
                                   related='config_id.journal_ids',
                                   readonly=True,
                                   string='Available Payment Methods')
    order_ids = fields.One2many('pos.order', 'session_id', string='Orders')
    statement_ids = fields.One2many('account.bank.statement',
                                    'pos_session_id',
                                    string='Bank Statement',
                                    readonly=True)
    picking_count = fields.Integer(compute='_compute_picking_count')
    rescue = fields.Boolean(
        string='Recovery Session',
        help="Auto-generated session for orphan orders, ignored in constraints",
        readonly=True,
        copy=False)

    _sql_constraints = [('uniq_name', 'unique(name)',
                         "The name of this POS Session must be unique !")]

    @api.multi
    def _compute_picking_count(self):
        for pos in self:
            pickings = pos.order_ids.mapped('picking_id').filtered(
                lambda x: x.state != 'done')
            pos.picking_count = len(pickings.ids)

    @api.multi
    def action_stock_picking(self):
        pickings = self.order_ids.mapped('picking_id').filtered(
            lambda x: x.state != 'done')
        action_picking = self.env.ref('stock.action_picking_tree_ready')
        action = action_picking.read()[0]
        action['context'] = {}
        action['domain'] = [('id', 'in', pickings.ids)]
        return action

    @api.depends('config_id', 'statement_ids')
    def _compute_cash_all(self):
        for session in self:
            session.cash_journal_id = session.cash_register_id = session.cash_control = False
            if session.config_id.cash_control:
                for statement in session.statement_ids:
                    if statement.journal_id.type == 'cash':
                        session.cash_control = True
                        session.cash_journal_id = statement.journal_id.id
                        session.cash_register_id = statement.id
                if not session.cash_control and session.state != 'closed':
                    raise UserError(
                        _("Cash control can only be applied to cash journals.")
                    )

    @api.constrains('user_id', 'state')
    def _check_unicity(self):
        # open if there is no session in 'opening_control', 'opened', 'closing_control' for one user
        if self.search_count([('state', 'not in',
                               ('closed', 'closing_control')),
                              ('user_id', '=', self.user_id.id),
                              ('rescue', '=', False)]) > 1:
            raise ValidationError(
                _("You cannot create two active sessions with the same responsible."
                  ))

    @api.constrains('config_id')
    def _check_pos_config(self):
        if self.search_count([('state', '!=', 'closed'),
                              ('config_id', '=', self.config_id.id),
                              ('rescue', '=', False)]) > 1:
            raise ValidationError(
                _("Another session is already opened for this point of sale."))

    @api.model
    def create(self, values):
        config_id = values.get('config_id') or self.env.context.get(
            'default_config_id')
        if not config_id:
            raise UserError(
                _("You should assign a Point of Sale to your session."))

        # journal_id is not required on the pos_config because it does not
        # exists at the installation. If nothing is configured at the
        # installation we do the minimal configuration. Impossible to do in
        # the .xml files as the CoA is not yet installed.
        pos_config = self.env['pos.config'].browse(config_id)
        ctx = dict(self.env.context, company_id=pos_config.company_id.id)
        if not pos_config.journal_id:
            default_journals = pos_config.with_context(ctx).default_get(
                ['journal_id', 'invoice_journal_id'])
            if (not default_journals.get('journal_id')
                    or not default_journals.get('invoice_journal_id')):
                raise UserError(
                    _("Unable to open the session. You have to assign a sales journal to your point of sale."
                      ))
            pos_config.with_context(ctx).sudo().write({
                'journal_id':
                default_journals['journal_id'],
                'invoice_journal_id':
                default_journals['invoice_journal_id']
            })
        # define some cash journal if no payment method exists
        if not pos_config.journal_ids:
            Journal = self.env['account.journal']
            journals = Journal.with_context(ctx).search([
                ('journal_user', '=', True), ('type', '=', 'cash')
            ])
            if not journals:
                journals = Journal.with_context(ctx).search([('type', '=',
                                                              'cash')])
                if not journals:
                    journals = Journal.with_context(ctx).search([
                        ('journal_user', '=', True)
                    ])
            if not journals:
                raise ValidationError(
                    _("No payment method configured! \nEither no Chart of Account is installed or no payment method is configured for this POS."
                      ))
            journals.sudo().write({'journal_user': True})
            pos_config.sudo().write({'journal_ids': [(6, 0, journals.ids)]})

        pos_name = self.env['ir.sequence'].with_context(ctx).next_by_code(
            'pos.session')
        if values.get('name'):
            pos_name += ' ' + values['name']

        statements = []
        ABS = self.env['account.bank.statement']
        uid = SUPERUSER_ID if self.env.user.has_group(
            'point_of_sale.group_pos_user') else self.env.user.id
        for journal in pos_config.journal_ids:
            # set the journal_id which should be used by
            # account.bank.statement to set the opening balance of the
            # newly created bank statement
            ctx['journal_id'] = journal.id if pos_config.cash_control and journal.type == 'cash' else False
            st_values = {
                'journal_id':
                journal.id,
                'user_id':
                self.env.user.id,
                'name':
                pos_name,
                'balance_start':
                self.env["account.bank.statement"]._get_opening_balance(
                    journal.id) if journal.type == 'cash' else 0
            }

            statements.append(
                ABS.with_context(ctx).sudo(uid).create(st_values).id)

        values.update({
            'name': pos_name,
            'statement_ids': [(6, 0, statements)],
            'config_id': config_id
        })

        res = super(PosSession,
                    self.with_context(ctx).sudo(uid)).create(values)
        if not pos_config.cash_control:
            res.action_pos_session_open()

        return res

    @api.multi
    def unlink(self):
        for session in self.filtered(lambda s: s.statement_ids):
            session.statement_ids.unlink()
        return super(PosSession, self).unlink()

    @api.multi
    def login(self):
        self.ensure_one()
        self.write({
            'login_number': self.login_number + 1,
        })

    @api.multi
    def action_pos_session_open(self):
        # second browse because we need to refetch the data from the DB for cash_register_id
        # we only open sessions that haven't already been opened
        for session in self.filtered(
                lambda session: session.state == 'opening_control'):
            values = {}
            if not session.start_at:
                values['start_at'] = fields.Datetime.now()
            values['state'] = 'opened'
            session.write(values)
            session.statement_ids.button_open()
        return True

    @api.multi
    def action_pos_session_closing_control(self):
        self._check_pos_session_balance()
        for session in self:
            session.write({
                'state': 'closing_control',
                'stop_at': fields.Datetime.now()
            })
            if not session.config_id.cash_control:
                session.action_pos_session_close()
        return True

    @api.multi
    def _check_pos_session_balance(self):
        for session in self:
            for statement in session.statement_ids:
                if (statement != session.cash_register_id) and (
                        statement.balance_end != statement.balance_end_real):
                    statement.write(
                        {'balance_end_real': statement.balance_end})

    @api.multi
    def action_pos_session_validate(self):
        self._check_pos_session_balance()
        self.action_pos_session_close()
        return True

    @api.multi
    def action_pos_session_close(self):
        # Close CashBox
        for session in self:
            company_id = session.config_id.company_id.id
            ctx = dict(self.env.context,
                       force_company=company_id,
                       company_id=company_id)
            ctx_notrack = dict(ctx, mail_notrack=True)
            for st in session.statement_ids:
                if abs(st.difference) > st.journal_id.amount_authorized_diff:
                    # The pos manager can close statements with maximums.
                    if not self.user_has_groups(
                            "point_of_sale.group_pos_manager"):
                        raise UserError(
                            _("Your ending balance is too different from the theoretical cash closing (%.2f), the maximum allowed is: %.2f. You can contact your manager to force it."
                              ) % (st.difference,
                                   st.journal_id.amount_authorized_diff))
                if (st.journal_id.type not in ['bank', 'cash']):
                    raise UserError(
                        _("The journal type for your payment method should be bank or cash."
                          ))
                st.with_context(ctx_notrack).sudo().button_confirm_bank()
        self.with_context(ctx)._confirm_orders()
        self.write({'state': 'closed'})
        return {
            'type': 'ir.actions.client',
            'name': 'Point of Sale Menu',
            'tag': 'reload',
            'params': {
                'menu_id': self.env.ref('point_of_sale.menu_point_root').id
            },
        }

    @api.multi
    def open_frontend_cb(self):
        if not self.ids:
            return {}
        for session in self.filtered(lambda s: s.user_id.id != self.env.uid):
            raise UserError(
                _("You cannot use the session of another user. This session is owned by %s. "
                  "Please first close this one to use this point of sale.") %
                session.user_id.name)
        return {
            'type': 'ir.actions.act_url',
            'target': 'self',
            'url': '/pos/web/',
        }

    @api.multi
    def open_cashbox(self):
        self.ensure_one()
        context = dict(self._context)
        balance_type = context.get('balance') or 'start'
        context['bank_statement_id'] = self.cash_register_id.id
        context['balance'] = balance_type
        context['default_pos_id'] = self.config_id.id

        action = {
            'name': _('Cash Control'),
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'account.bank.statement.cashbox',
            'view_id':
            self.env.ref('account.view_account_bnk_stmt_cashbox').id,
            'type': 'ir.actions.act_window',
            'context': context,
            'target': 'new'
        }

        cashbox_id = None
        if balance_type == 'start':
            cashbox_id = self.cash_register_id.cashbox_start_id.id
        else:
            cashbox_id = self.cash_register_id.cashbox_end_id.id
        if cashbox_id:
            action['res_id'] = cashbox_id

        return action