예제 #1
0
    def compute_landed_cost(self):
        AdjustementLines = self.env['stock.valuation.adjustment.lines']
        AdjustementLines.search([('cost_id', 'in', self.ids)]).unlink()

        digits = dp.get_precision('Product Price')(self._cr)
        towrite_dict = {}
        for cost in self.filtered(lambda cost: cost.picking_ids):
            total_qty = 0.0
            total_cost = 0.0
            total_weight = 0.0
            total_volume = 0.0
            total_line = 0.0
            all_val_line_values = cost.get_valuation_lines()
            for val_line_values in all_val_line_values:
                for cost_line in cost.cost_lines:
                    val_line_values.update({'cost_id': cost.id, 'cost_line_id': cost_line.id})
                    self.env['stock.valuation.adjustment.lines'].create(val_line_values)
                total_qty += val_line_values.get('quantity', 0.0)
                total_cost += val_line_values.get('former_cost', 0.0)
                total_weight += val_line_values.get('weight', 0.0)
                total_volume += val_line_values.get('volume', 0.0)
                total_line += 1

            for line in cost.cost_lines:
                value_split = 0.0
                for valuation in cost.valuation_adjustment_lines:
                    value = 0.0
                    if valuation.cost_line_id and valuation.cost_line_id.id == line.id:
                        if line.split_method == 'by_quantity' and total_qty:
                            per_unit = (line.price_unit / total_qty)
                            value = valuation.quantity * per_unit
                        elif line.split_method == 'by_weight' and total_weight:
                            per_unit = (line.price_unit / total_weight)
                            value = valuation.weight * per_unit
                        elif line.split_method == 'by_volume' and total_volume:
                            per_unit = (line.price_unit / total_volume)
                            value = valuation.volume * per_unit
                        elif line.split_method == 'equal':
                            value = (line.price_unit / total_line)
                        elif line.split_method == 'by_current_cost_price' and total_cost:
                            per_unit = (line.price_unit / total_cost)
                            value = valuation.former_cost * per_unit
                        else:
                            value = (line.price_unit / total_line)

                        if digits:
                            value = tools.float_round(value, precision_digits=digits[1], rounding_method='UP')
                            fnc = min if line.price_unit > 0 else max
                            value = fnc(value, line.price_unit - value_split)
                            value_split += value

                        if valuation.id not in towrite_dict:
                            towrite_dict[valuation.id] = value
                        else:
                            towrite_dict[valuation.id] += value
        if towrite_dict:
            for key, value in towrite_dict.items():
                AdjustementLines.browse(key).write({'additional_landed_cost': value})
        return True
예제 #2
0
    def do_change_standard_price(self, new_price, account_id):
        """ Changes the Standard Price of Product and creates an account move accordingly."""
        AccountMove = self.env["account.move"]

        locations = self.env["stock.location"].search(
            [("usage", "=", "internal"), ("company_id", "=", self.env.user.company_id.id)]
        )

        product_accounts = {(product.id, product.product_tmpl_id.get_product_accounts()) for product in self}
        price_precision = dp.get_precision("Product Price")

        for location in locations:
            for product in self.with_context(location=location.id, compute_child=False):
                diff = product.standard_price - new_price
                if tools.float_is_zero(diff, precision_digits=price_precision):
                    raise UserError(_("No difference between standard price and new price!"))
                qty_available = product.qty_available
                if qty_available:
                    # Accounting Entries
                    if diff * qty_available > 0:
                        debit_account_id = account_id
                        credit_account_id = product_accounts[product.id]["stock_valuation"].id
                    else:
                        debit_account_id = product_accounts[product.id]["stock_valuation"].id
                        credit_account_id = account_id

                    move_vals = {
                        "journal_id": product_accounts[product.id]["stock_journal"].id,
                        "company_id": location.company_id.id,
                        "line_ids": [
                            (
                                0,
                                0,
                                {
                                    "name": _("Standard Price changed"),
                                    "account_id": debit_account_id,
                                    "debit": abs(diff * qty_available),
                                    "credit": 0,
                                },
                            ),
                            (
                                0,
                                0,
                                {
                                    "name": _("Standard Price changed"),
                                    "account_id": credit_account_id,
                                    "debit": 0,
                                    "credit": abs(diff * qty_available),
                                },
                            ),
                        ],
                    }
                    move = AccountMove.create(move_vals)
                    move.post()

        self.write({"standard_price": new_price})
        return True
예제 #3
0
    def _onchange_partner(self):
        if self.partner_id:
            self.delivery_id = self.partner_id  # MAJ d'un autre champ
            # M2M : 2 possibilités :
            # - liste d'IDs, mais ça va AJOUTER les ids, comme (4, [IDs])
            # - [(6, 0, [IDs])], ce qui va remplacer les ids
            # (cf module product_category_tax dans akretion/odoo-usability)
            # On utilise un autre M2M pour le mettre à jour, on peut faire
            # self.champ_M2M_ids.ids -> ça donne la liste des IDs
            # M2O : recordset (ou ID)
            # O2M : exemple v10 dans purchase/models/account_invoice.py
            # méthode purchase_order_change()
            # là, Odoo va jouer automatiquement le @api.onchange du champ delivery_id
            # pas besoin d'appeler le onchange de delivery_id dans notre code
        # Here, all form values are set on self
        # assigned values are not written to DB, but returned to the client
        # It is not possible to output a warning
        # It is not possible to put a raise UserError()
        # in this function (it will crash odoo)
        res = {'warning':
            {'title': _('Be careful'),
            {'message': _('here is the msg')}}
        # pour un domaine
        res = {'domain': {
            'champ1': "[('product_id', '=', product_id)]",
            'champ2': "[]"},
            }
        return res
        # si je n'ai ni warning ni domain, je n'ai pas besoin de faire un return

    # Fonction on_change déclarée dans la vue form/tree
    @api.multi
    def product_id_change(self, cr, uid, ids, champ1, champ2, context):
        # ATTENTION : a priori, on ne doit pas utiliser ids dans le code de la
        # fonction, car quand on fait un on_change avant le save, ids = []
        # Dans la vue XML :
        # <field name="product_id"
        #        on_change="product_id_change(champ1, champ2, context)" />
        # Piège : quand un champ float est passé dans un on_change,
        # si la personne avait tapé un entier, il va être passé en argument en
        # tant que integer et non en tant que float!

        raise orm.except_orm()
        # => il ne remet PAS l'ancienne valeur qui a déclanché le on_change

        # Pour mettre à jour des valeurs :
        return {'value': {'champ1': updated_value1, 'champ2': updated_value2}}
        # => à savoir : les onchange de 'champ1' et 'champ2' sont joués à
        # leur tour car leur valeur a été changée
        # si ces nouveaux on_change changent le product_id,
        # le product_id_change ne sera pas rejoué

        # Pour mettre un domaine :
        return {'domain': {
            'champ1': "[('product_id', '=', product_id)]",
            'champ2': "[]"},
            }
        # l'intégralité du domaine est dans une string

        # Pour retourner un message de warning :
        return {'warning': {
            'title': _('Le titre du msg de warn'),
            'message': _("Ce que j'ai à te dire %s") % (text)}}
        # Pour ne rien faire
        return False  # return True, ça marche en 7.0 mais ça bug en 6.1

    # La fonction de calcul du champ function price_subtotal
    @api.one  # auto-loop decorator
    @api.depends('price_unit', 'discount', 'invoice_line_tax_id', 'quantity',
        'product_id', 'invoice_id.partner_id', 'invoice_id.currency_id')
    # @api.depends est utilisé pour: invalidation cache, recalcul, onchange
    # donc, maintenant, le fait d'avoir un champ calculé fait qu'il est
    # automatiquement mis à jour dans la vue quand un de ses champs 'depends'
    # est modifié ! COOOOOL !
    # ATTENTION : si chgt de @api.depends, faire -u module !
    # Pour un one2many : ne PAS juste indiquer le nom du champ o2m, sinon il ne fait rien
    # il faut aussi indiquer un champ sur le O2M. Exemple : 'line_ids.request_id'
    # Apparemment, on peut mettre dans @api.depends un champ fonction stocké et ça va bien
    # faire le recalcul en cascade
    # (ça n'a pas l'air de marcher qd on met un api.depends sur un champ non stocké)
    def _compute_price(self):
        price = self.price_unit * (1 - (self.discount or 0.0) / 100.0)
        taxes = self.invoice_line_tax_id.compute_all(price, self.quantity, product=self.product_id, partner=self.invoice_id.partner_id)
        self.price_subtotal = taxes['total']  # calcul et stockage de la valeur
        self.second_field = 'iuit'  # calcul et stockage d'un 2e champ
                                    # equivalent de multi='pouet'
        # Pour un champ O2M ou M2M, envoyer un recordset multiple ou une liste d'IDS
        # pour un champ M2O, donner le recordset ou l'ID
        if self.invoice_id:
            self.price_subtotal = self.invoice_id.currency_id.round(self.price_subtotal)
        # Pas besoin de return !
        # on ne peut PAS faire un self.write({}) dans la fonction de calcul d'un champ fonction

    # Pour un champ fonction, on peut aussi faire @api.multi:
    # untaxed = fields.Float(compute='_amounts')
    # taxes = fields.Float(compute='_amounts')
    # total = fields.Float(compute='_amounts')
    @api.multi
    @api.depends('lines.amount', 'lines.taxes')
    def _amounts(self):
        for order in self:
            order.untaxed = sum(line.amount for line in order.lines)
            order.taxes = sum(line.taxes for line in order.lines)
            order.total = order.untaxed + order + taxes

    # Champ fonction inverse='_inverse_price'
    @api.one
    def _inverse_loud(self):
        self.name = (self.loud or '').lower()  # MAJ du ou des autres champs

    # Champ fonction search='_search_price'
    def _search_loud(self, operator, value):
        if value is not False:
            value = value.lower()
        today = fields.Date.context_today(self)
        self._cr.execute('SELECT id FROM [cur_obj] WHERE (fortress_type <> %s OR (fortress_type = %s AND effectivity_date is not null)) AND (end_date is null OR end_date > %s)', (today, ))
        res_ids = [x[0] for x in self._cr.fetchall()]
        res = [('id', 'in', res_ids)] # recherche sur les autres champs
        return res

    # Fonction default=_default_account
    @api.model
    def _default_account(self):
        return valeur_par_defaut
        # M2O : retourne un recordset ou un ID (ou False)
        # (NOTE: apparemment, en v8, il veut un ID)
        # OUTDATED (?) : ATTENTION, si on veut un M2O à False, il ne pas que la fonction
        #       _default_account retourne False mais self.env['..'].browse(False)
        # O2M : retourne une liste de dict contenant la valeur des champs
        # M2M : retourne un recordset multiple ?
        # date : string ou objet datetime

    # Fonction pour fields.selection
    @api.model
    def _type_list_get(self):
        return [('key1', _('String1')), ('key2', _('String2'))]

    ### CHAMPS
    # id, create_uid, write_uid, create_date et write_date
    # sont déjà utilisable dans le code python sans re-définition
    active = fields.Boolean(default=True)
    # Par défaut, string = nom du champ avec majuscule pour chaque début de mot
    login = fields.Char(
        string='Login', size=16, translate=True, required=True,
        help="My help message")
    display_name = fields.Char(
        string='Display Name', compute='_compute_display_name',
        readonly=True, store=True)
    comment = fields.Text(string='Comment', translate=True)
    html = fields.Html(string='report', translate=True)
    code_digits = fields.Integer(
        string='# of Digits', track_visibility='onchange', default=12,
        groups='base.group_user')
    # OU groups=['base.group_user', 'base.group_hr_manager']
    # groups = XMLID : restriction du read/write et invisible ds les vues
    sequence = fields.Integer(default=10)
    # track_visibility = always ou onchange
    amount_untaxed = fields.Float(
        'Amount untaxed', digits=dp.get_precision('Account'),
        group_operator="avg")  # Utile pour un pourcentage par exemple
    # digits=(precision, scale)   exemple (16, 2)
    # Scale est le nombre de chiffres après la virgule
    # quand le float est un fields.float ou un fields.function,
    # on met l'option : digits=dp.get_precision('Account')
    # Autres valeurs possibles pour get_precision : product/product_data.xml
    # Product Price, Discount, Stock Weight, Product Unit of Measure,
    # Product UoS (v8 only)
    # fields.Monetary is only in version >= 9.0
    debit = fields.Monetary(default=0.0, currency_field='company_currency_id')
    start_date = fields.Date(
        string='Start Date', copy=False, default=fields.Date.context_today,
        index=True)
    # similaire : fields.Datetime and fields.Time
    # index=True => the field will be indexed in the database
    # (much faster when you search on that field)
    type = fields.Selection([
        ('import', 'Import'),
        ('export', 'Export'),
        ], string="Type",
        default=lambda self: self._context.get('type', 'export'))
    # FIELDS.SELECTION ac selection dynamique :
    # type = fields.Selection('_type_list_get', string='Type', help='Pouet'),
    # Plus besoin de la double fonction pour que la 2e soit héritable
    # Pour ajouter des champs à un fields.Selection existant:
    # fields.Selection(
    #    selection_add=[('new_key1', 'My new key1'), ('new_key2', 'My New Key2')])
    picture = fields.Binary(string='Picture')
    # Pour fields.binary, il existe une option filters='*.png, *.gif',
    # qui restreint les formats de fichiers sélectionnables dans
    # la boite de dialogue, mais ça ne marche pas en GTK (on
    # ne peut rien sélectionner) et c'est pas supporté en Web, cf
    # https://bugs.launchpad.net/openobject-server/+bug/1076895
    picture_filename = fields.Char(string='Filename')
    # Les champs "picture" et "picture_filename" sont liés ensemble dans la vue
    # via la balise filename="picture_filename" sur le champ 'picture'
    # Il faut que le champ 'picture_filename' soit présent dans la vue
    # (il peut être invisible)
    # Pour un fichier à télécharger d'Odoo, le nom du fichier aura la valeur de
    # picture_filename
    # Pour un fichier à uploader dans Odoo, 'picture_filename' vaudra le nom
    # du fichier uploadé par l'utilisateur

    # Exemple de champ fonction stocké
    price_subtotal = fields.Float(
        string='Amount', digits= dp.get_precision('Account'),
        store=True, readonly=True, compute='_compute_price')
    # Exemple de champ function non stocké avec fonction inverse
    loud = fields.Char(
        store=False, compute='_compute_loud', inverse='_inverse_loud',
        search='_search_loud')
    account_id = fields.Many2one('account.account', string='Account',
        required=True, domain=[('type', 'not in', ['view', 'closed'])],
        default=lambda self: self._default_account())
        # L'utilisation de lambda permet d'hériter la fonction _default_account() sans
        # hériter le champ. Sinon, on peut aussi utiliser default=_default_account
    company_id = fields.Many2one(
        'res.company', string='Company',
        ondelete='cascade', required=True,
        default=lambda self: self.env['res.company']._company_default_get(
            'product.code'))
        # si on veut que tous les args soient nommés : comodel_name='res.company'
    user_id = fields.Many2one(
        'res.users', string='Salesman', default=lambda self: self.env.user)
    # ATTENTION : si j'ai déjà un domaine sur la vue,
    # c'est le domaine sur la vue qui prime !
    # ondelete='cascade' :
    # le fait de supprimer la company va supprimer l'objet courant !
    # ondelete='set null' (default)
    # si on supprime la company, le champ company_id est mis à 0
    # ondelete='restrict' :
    # si on supprime la company, ça déclanche une erreur d'intégrité !

    # Champ Relation
    company_currency_id = fields.Many2one(
        'res.currency', string='Currency', related='company_id.currency_id',
        store=True, compute_sudo=True)
    # ATTENTION, en nouvelle API, on ne peut PAS faire un fields.Char qui
    # soit un related d'un fields.Selection (bloque le démarrage d'Odoo
    # sans message d'erreur !)

    line_ids = fields.One2many(
        'product.code.line', 'parent_id', string='Product lines',
        states={'done': [('readonly', True)]}, copy=True)
        # OU comodel_name='product.code.line', inverse_name='parent_id'
    # 2e arg = nom du champ sur l'objet destination qui est le M20 inverse
    # en v8 :
    # copy=True pour que les lignes soient copiées lors d'un duplicate
    # sinon, mettre copy=False (ça ne peut être qu'un booléen)
    # Valeur par défaut du paramètre "copy": True for normal fields, False for
    # one2many and computed fields, including property fields and related fields
    # ATTENTION : pour que states={} marche sur le champ A et que le
    # champ A est dans la vue tree, alors il faut que le champ "state"
    # soit aussi dans la vue tree.

    partner_ids = fields.Many2many(
        'res.partner', 'product_code_partner_rel', 'code_id', 'partner_id',
        'Related Partners')
    # 2e arg = nom de la table relation
    # 3e arg ou id1 = nom de la colonne dans la table relation
    # pour stocker l'ID du product.code
    # 4e arg ou id2 = nom de la colonne dans la table relation
    # pour stocker l'ID du res.partner
    # OU
    partner_ids = fields.Many2many(
        'res.partner', column1='code_id', column2='partner_id',
        string='Related Partners')
    # OU
    partner_ids = fields.Many2many(
        'res.partner', string='Related Partners')
    # Pour les 2 dernières définitions du M2M, il ne faut pas avoir
    # plusieurs champs M2M qui pointent du même obj vers le même obj

    # Champ property: il suffit de définit le champ comme un champ normal
    # et d'ajouter un argument company_dependent=True
    # Quand on veut lire la valeur d'un champ property dans une société
    # qui n'est pas celle de l'utilisateur, il faut passer dans le context
    # 'force_company': 8  (8 = ID de la company)
    }
예제 #4
0
class AccountVoucherWizard(models.TransientModel):
    _name = "account.voucher.wizard"

    journal_id = fields.Many2one('account.journal', 'Journal', required=True)
    amount_total = fields.Float(_('Amount total'), readonly=True)
    amount_advance = fields.Float(_('Amount advanced'),
                                  required=True,
                                  digits=dp.get_precision('Product Price'))
    date = fields.Date("Date",
                       required=True,
                       default=fields.Date.context_today)
    exchange_rate = fields.Float("Exchange rate",
                                 digits=(16, 6),
                                 default=1.0,
                                 readonly=True)
    currency_id = fields.Many2one("res.currency", "Currency", readonly=True)
    currency_amount = fields.Float("Curr. amount",
                                   digits=(16, 2),
                                   readonly=True)
    payment_ref = fields.Char("Ref.")

    @api.constrains('amount_advance')
    def check_amount(self):
        if self.amount_advance <= 0:
            raise exceptions.ValidationError(
                _("Amount of advance must be "
                  "positive."))
        if self.env.context.get('active_id', False):
            order = self.env["sale.order"].\
                browse(self.env.context['active_id'])
            if self.amount_advance > order.amount_resisual:
                raise exceptions.ValidationError(
                    _("Amount of advance is "
                      "greater than residual "
                      "amount on sale"))

    @api.model
    def default_get(self, fields):
        res = super(AccountVoucherWizard, self).default_get(fields)
        sale_ids = self.env.context.get('active_ids', [])
        if not sale_ids:
            return res
        sale_id = sale_ids[0]

        sale = self.env['sale.order'].browse(sale_id)

        amount_total = sale.amount_resisual

        if 'amount_total' in fields:
            res.update({
                'amount_total': amount_total,
                'currency_id': sale.pricelist_id.currency_id.id
            })

        return res

    @api.onchange('journal_id', 'date')
    def onchange_date(self):
        if self.currency_id:
            self.exchange_rate = 1.0 / \
                (self.env["res.currency"].with_context(date=self.date).
                 _get_conversion_rate(self.currency_id,
                                      (self.journal_id.currency_id or
                                       self.env.user.company_id.
                                       currency_id))
                 or 1.0)
            self.currency_amount = self.amount_advance * \
                (1.0 / self.exchange_rate)
        else:
            self.exchange_rate = 1.0

    @api.onchange('amount_advance')
    def onchange_amount(self):
        self.currency_amount = self.amount_advance * (1.0 / self.exchange_rate)

    @api.multi
    def make_advance_payment(self):
        """Create customer paylines and validates the payment"""
        payment_obj = self.env['account.payment']
        sale_obj = self.env['sale.order']

        sale_ids = self.env.context.get('active_ids', [])
        if sale_ids:
            sale_id = sale_ids[0]
            sale = sale_obj.browse(sale_id)

            partner_id = sale.partner_id.id
            date = self[0].date
            company = sale.company_id

            payment_res = {
                'payment_type':
                'inbound',
                'partner_id':
                partner_id,
                'partner_type':
                'customer',
                'journal_id':
                self[0].journal_id.id,
                'company_id':
                company.id,
                'currency_id':
                sale.pricelist_id.currency_id.id,
                'payment_date':
                date,
                'amount':
                self[0].amount_advance,
                'sale_id':
                sale.id,
                'name':
                _("Advance Payment") + " - " + sale.name,
                'communication':
                self[0].payment_ref or sale.name,
                'payment_method_id':
                self.env.ref('account.account_payment_method_manual_in').id
            }
            payment = payment_obj.create(payment_res)
            payment.post()

        return {
            'type': 'ir.actions.act_window_close',
        }
예제 #5
0
class SellOrder(models.Model):
    _name = 'sell.order'
    _description = u'销货订单'
    _inherit = ['mail.thread']
    _order = 'date desc, id desc'

    @api.one
    @api.depends('line_ids.subtotal', 'discount_amount')
    def _compute_amount(self):
        '''当订单行和优惠金额改变时,改变成交金额'''
        total = sum(line.subtotal for line in self.line_ids)
        self.amount = total - self.discount_amount

    @api.one
    @api.depends('line_ids.quantity')
    def _compute_qty(self):
        '''当订单行数量改变时,更新总数量'''
        self.total_qty = sum(line.quantity for line in self.line_ids)

    @api.one
    @api.depends('line_ids.quantity', 'line_ids.quantity_out')
    def _get_sell_goods_state(self):
        '''返回发货状态'''
        if all(line.quantity_out == 0 for line in self.line_ids):
            self.goods_state = u'未出库'
        elif any(line.quantity > line.quantity_out for line in self.line_ids):
            self.goods_state = u'部分出库'
        else:
            self.goods_state = u'全部出库'

    @api.one
    @api.depends('partner_id')
    def _compute_currency_id(self):
        self.currency_id = self.partner_id.c_category_id.account_id.currency_id.id or self.partner_id.s_category_id.account_id.currency_id.id

    @api.model
    def _default_warehouse(self):
        return self._default_warehouse_impl()

    @api.model
    def _default_warehouse_impl(self):
        if self.env.context.get('warehouse_type'):
            return self.env['warehouse'].get_warehouse_by_type(
                self.env.context.get('warehouse_type'))

    @api.one
    def _get_received_amount(self):
        '''计算销货订单收款/退款状态'''
        deliverys = self.env['sell.delivery'].search(
            [('order_id', '=', self.id)])
        money_order_rows = self.env['money.order'].search([('sell_id', '=', self.id),
                                                           ('reconciled', '=', 0),
                                                           ('state', '=', 'done')])
        self.received_amount = sum([delivery.invoice_id.reconciled for delivery in deliverys]) +\
            sum([order_row.amount for order_row in money_order_rows])

    @api.multi
    @api.depends('delivery_ids')
    def _compute_delivery(self):
        for order in self:
            order.delivery_count = len([deli for deli in order.delivery_ids if not deli.is_return])
            order.return_count = len([deli for deli in order.delivery_ids if deli.is_return])

    @api.one
    @api.depends('line_ids.goods_id', 'line_ids.quantity')
    def _compute_net_weight(self):
        '''计算净重合计'''
        self.net_weight = sum(line.goods_id.net_weight * line.quantity for line in self.line_ids)

    partner_id = fields.Many2one('partner', u'客户',
                                 ondelete='restrict', states=READONLY_STATES,
                                 help=u'签约合同的客户')
    contact = fields.Char(u'联系人', states=READONLY_STATES,
                          help=u'客户方的联系人')
    address_id = fields.Many2one('partner.address', u'地址', states=READONLY_STATES,
                                 domain="[('partner_id', '=', partner_id)]",
                                 help=u'联系地址')
    mobile = fields.Char(u'手机', states=READONLY_STATES,
                         help=u'联系手机')
    user_id = fields.Many2one(
        'res.users',
        u'销售员',
        ondelete='restrict',
        states=READONLY_STATES,
        default=lambda self: self.env.user,
        help=u'单据经办人',
    )
    date = fields.Date(u'单据日期',
                       required=True,
                       states=READONLY_STATES,
                       default=lambda self: fields.Date.context_today(self),
                       index=True,
                       copy=False,
                       help=u"默认是订单创建日期")
    delivery_date = fields.Date(
        u'要求交货日期',
        required=True,
        states=READONLY_STATES,
        default=lambda self: fields.Date.context_today(self),
        index=True,
        copy=False,
        help=u"订单的要求交货日期")
    type = fields.Selection([('sell', u'销货'), ('return', u'退货')], u'类型',
                            default='sell', states=READONLY_STATES,
                            help=u'销货订单的类型,分为销货或退货')
    ref = fields.Char(u'客户订单号')
    warehouse_id = fields.Many2one('warehouse',
                                   u'调出仓库',
                                   required=True,
                                   ondelete='restrict',
                                   states=READONLY_STATES,
                                   default=_default_warehouse,
                                   help=u'商品将从该仓库调出')
    name = fields.Char(u'单据编号', index=True, copy=False,
                       default='/', help=u"创建时它会自动生成下一个编号")
    line_ids = fields.One2many('sell.order.line', 'order_id', u'销货订单行',
                               states=READONLY_STATES, copy=True,
                               help=u'销货订单的明细行,不能为空')
    note = fields.Text(u'备注', help=u'单据备注')
    discount_rate = fields.Float(u'优惠率(%)', states=READONLY_STATES,
                                 help=u'整单优惠率')
    discount_amount = fields.Float(u'抹零', states=READONLY_STATES,
                                   track_visibility='always',
                                   digits=dp.get_precision('Amount'),
                                   help=u'整单优惠金额,可由优惠率自动计算出来,也可手动输入')
    amount = fields.Float(string=u'成交金额', store=True, readonly=True,
                          compute='_compute_amount', track_visibility='always',
                          digits=dp.get_precision('Amount'),
                          help=u'总金额减去优惠金额')
    total_qty = fields.Float(string=u'数量合计', store=True, readonly=True,
                          compute='_compute_qty',
                          track_visibility='always',
                          digits=dp.get_precision('Quantity'),
                          help=u'数量总计')
    pre_receipt = fields.Float(u'预收款', states=READONLY_STATES,
                               digits=dp.get_precision('Amount'),
                               help=u'输入预收款确认销货订单,会产生一张收款单')
    bank_account_id = fields.Many2one('bank.account', u'结算账户',
                                      ondelete='restrict',
                                      help=u'用来核算和监督企业与其他单位或个人之间的债权债务的结算情况')
    approve_uid = fields.Many2one('res.users', u'确认人', copy=False,
                                  ondelete='restrict',
                                  help=u'确认单据的人')
    state = fields.Selection(SELL_ORDER_STATES, u'确认状态', readonly=True,
                             help=u"销货订单的确认状态", index=True,
                             copy=False, default='draft')
    goods_state = fields.Char(u'发货状态', compute=_get_sell_goods_state,
                              default=u'未出库',
                              store=True,
                              help=u"销货订单的发货状态", index=True, copy=False)
    cancelled = fields.Boolean(u'已终止',
                               help=u'该单据是否已终止')
    currency_id = fields.Many2one('res.currency',
                                  u'外币币别',
                                  compute='_compute_currency_id',
                                  store=True,
                                  readonly=True,
                                  help=u'外币币别')
    company_id = fields.Many2one(
        'res.company',
        string=u'公司',
        change_default=True,
        default=lambda self: self.env['res.company']._company_default_get())
    received_amount = fields.Float(
        u'已收金额',  compute=_get_received_amount, readonly=True)
    delivery_ids = fields.One2many(
        'sell.delivery', 'order_id', string=u'发货单', copy=False)
    delivery_count = fields.Integer(
        compute='_compute_delivery', string=u'发货单数量', default=0)
    return_count = fields.Integer(
        compute='_compute_delivery', string=u'退货单数量', default=0)
    pay_method = fields.Many2one('core.value',
                                 string=u'付款方式',
                                 ondelete='restrict',
                                 domain=[('type', '=', 'pay_method')],
                                 context={'type': 'pay_method'})
    express_type = fields.Char(u'承运商')
    money_order_id = fields.Many2one(
        'money.order',
        u'预收款单',
        readonly=True,
        copy=False,
        help=u'输入预收款确认时产生的预收款单')
    net_weight = fields.Float(
        string=u'净重合计', compute='_compute_net_weight', store=True)

    @api.onchange('address_id')
    def onchange_partner_address(self):
        ''' 选择地址填充 联系人、电话 '''
        if self.address_id:
            self.contact = self.address_id.contact
            self.mobile = self.address_id.mobile

    @api.onchange('partner_id')
    def onchange_partner_id(self):
        ''' 选择客户带出其默认地址信息 '''
        if self.partner_id:
            self.contact = self.partner_id.contact
            self.mobile = self.partner_id.mobile

            for child in self.partner_id.child_ids:
                if child.is_default_add:
                    self.address_id = child.id
            if self.partner_id.child_ids and not any([child.is_default_add for child in self.partner_id.child_ids]):
                partners_add = self.env['partner.address'].search(
                    [('partner_id', '=', self.partner_id.id)], order='id')
                self.address_id = partners_add[0].id

            for line in self.line_ids:
                line.tax_rate = line.goods_id.get_tax_rate(line.goods_id, self.partner_id, 'sell')

            address_list = [
                child_list.id for child_list in self.partner_id.child_ids]
            if address_list:
                return {'domain': {'address_id': [('id', 'in', address_list)]}}
            else:
                self.address_id = False

    @api.onchange('discount_rate', 'line_ids')
    def onchange_discount_rate(self):
        '''当优惠率或销货订单行发生变化时,单据优惠金额发生变化'''
        total = sum(line.subtotal for line in self.line_ids)
        self.discount_amount = total * self.discount_rate * 0.01

    def _get_vals(self):
        '''返回创建 money_order 时所需数据'''
        flag = (self.type == 'sell' and 1 or -1)  # 用来标志发库或退货
        amount = flag * self.amount
        this_reconcile = flag * self.pre_receipt
        money_lines = [{
            'bank_id': self.bank_account_id.id,
            'amount': this_reconcile,
        }]
        return {
            'partner_id': self.partner_id.id,
            'date': fields.Date.context_today(self),
            'line_ids':
            [(0, 0, line) for line in money_lines],
            'amount': amount,
            'reconciled': this_reconcile,
            'to_reconcile': amount,
            'state': 'draft',
            'origin_name': self.name,
            'sell_id': self.id,
        }

    def generate_receipt_order(self):
        '''由销货订单生成收款单'''
        # 发库单/退货单
        if self.pre_receipt:
            money_order = self.with_context(type='get').env['money.order'].create(
                self._get_vals()
            )
            money_order.money_order_done()
            return money_order

    @api.one
    def sell_order_done(self):
        '''确认销货订单'''
        if self.state == 'done':
            raise UserError(u'请不要重复确认!')
        if not self.line_ids:
            raise UserError(u'请输入商品明细行!')
        for line in self.line_ids:
            # 检查属性是否填充,防止无权限人员不填就可以保存
            if line.using_attribute and not line.attribute_id:
                raise UserError(u'请输入商品:%s 的属性' % line.goods_id.name)
            if line.quantity <= 0 or line.price_taxed < 0:
                raise UserError(u'商品 %s 的数量和含税单价不能小于0!' % line.goods_id.name)
            if line.tax_amount > 0 and self.currency_id:
                raise UserError(u'外贸免税!')
        if not self.bank_account_id and self.pre_receipt:
            raise UserError(u'预付款不为空时,请选择结算账户!')
        # 销售预收款生成收款单
        money_order = self.generate_receipt_order()
        self.sell_generate_delivery()

        self.approve_uid = self._uid
        self.write({
            'money_order_id': money_order and money_order.id,
            'state': 'done',  # 为保证审批流程顺畅,否则,未审批就可审核
        })

    @api.one
    def sell_order_draft(self):
        '''撤销确认销货订单'''
        if self.state == 'draft':
            raise UserError(u'请不要重复撤销确认!')
        if self.goods_state != u'未出库':
            raise UserError(u'该销货订单已经发货,不能撤销确认!')
        # 查找产生的发货单并删除
        delivery = self.env['sell.delivery'].search(
            [('order_id', '=', self.name)])
        delivery.unlink()
        # 查找产生的收款单并删除
        if self.money_order_id:
            self.money_order_id.money_order_draft()
            self.money_order_id.unlink()
        self.approve_uid = ''
        self.state = 'draft'

    @api.one
    def get_delivery_line(self, line, single=False):
        '''返回销售发货/退货单行'''
        qty = 0
        discount_amount = 0
        if single:
            qty = 1
            discount_amount = line.discount_amount \
                / ((line.quantity - line.quantity_out) or 1)
        else:
            qty = line.quantity - line.quantity_out
            discount_amount = line.discount_amount

        return {
            'type': self.type == 'sell' and 'out' or 'in',
            'sell_line_id': line.id,
            'goods_id': line.goods_id.id,
            'attribute_id': line.attribute_id.id,
            'uos_id': line.goods_id.uos_id.id,
            'goods_qty': qty,
            'uom_id': line.uom_id.id,
            'cost_unit': line.goods_id.cost,
            'price': line.price,
            'price_taxed': line.price_taxed,
            'discount_rate': line.discount_rate,
            'discount_amount': discount_amount,
            'tax_rate': line.tax_rate,
            'note': line.note or '',
        }

    def _generate_delivery(self, delivery_line):
        '''根据明细行生成发货单或退货单'''
        # 如果退货,warehouse_dest_id,warehouse_id要调换
        warehouse = (self.type == 'sell'
                     and self.warehouse_id
                     or self.env.ref("warehouse.warehouse_customer"))
        warehouse_dest = (self.type == 'sell'
                          and self.env.ref("warehouse.warehouse_customer")
                          or self.warehouse_id)
        rec = (self.type == 'sell' and self.with_context(is_return=False)
               or self.with_context(is_return=True))
        delivery_id = rec.env['sell.delivery'].create({
            'partner_id': self.partner_id.id,
            'warehouse_id': warehouse.id,
            'warehouse_dest_id': warehouse_dest.id,
            'user_id': self.user_id.id,
            'date': self.delivery_date,
            'order_id': self.id,
            'ref':self.ref,
            'origin': 'sell.delivery',
            'note': self.note,
            'discount_rate': self.discount_rate,
            'discount_amount': self.discount_amount,
            'currency_id': self.currency_id.id,
            'contact': self.contact,
            'address_id': self.address_id.id,
            'mobile': self.mobile,
            'express_type': self.express_type,
        })
        if self.type == 'sell':
            delivery_id.write({'line_out_ids': [
                (0, 0, line[0]) for line in delivery_line]})
        else:
            delivery_id.write({'line_in_ids': [
                (0, 0, line[0]) for line in delivery_line]})
        return delivery_id

    @api.one
    def sell_generate_delivery(self):
        '''由销货订单生成销售发货单'''
        delivery_line = []  # 销售发货单行

        for line in self.line_ids:
            # 如果订单部分出库,则点击此按钮时生成剩余数量的出库单
            to_out = line.quantity - line.quantity_out
            if to_out <= 0:
                continue
            if line.goods_id.force_batch_one:
                i = 0
                while i < to_out:
                    i += 1
                    delivery_line.append(
                        self.get_delivery_line(line, single=True))
            else:
                delivery_line.append(
                    self.get_delivery_line(line, single=False))

        if not delivery_line:
            return {}
        delivery_id = self._generate_delivery(delivery_line)
        view_id = (self.type == 'sell'
                   and self.env.ref('sell.sell_delivery_form').id
                   or self.env.ref('sell.sell_return_form').id)
        name = (self.type == 'sell' and u'销售发货单' or u'销售退货单')
        return {
            'name': name,
            'view_type': 'form',
            'view_mode': 'form',
            'view_id': False,
            'views': [(view_id, 'form')],
            'res_model': 'sell.delivery',
            'type': 'ir.actions.act_window',
            'domain': [('id', '=', delivery_id)],
            'target': 'current',
        }

    @api.multi
    def action_view_delivery(self):
        '''
        This function returns an action that display existing deliverys of given sells order ids.
        When only one found, show the delivery immediately.
        '''
        self.ensure_one()
        action = {
            'name': u'销售发货单',
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'sell.delivery',
            'view_id': False,
            'target': 'current',
        }
        delivery_ids = [delivery.id for delivery in self.delivery_ids if not delivery.is_return]
        if len(delivery_ids) > 1:
            action['domain'] = "[('id','in',[" + \
                ','.join(map(str, delivery_ids)) + "])]"
            action['view_mode'] = 'tree,form'
        elif len(delivery_ids) == 1:
            view_id = self.env.ref('sell.sell_delivery_form').id
            action['views'] = [(view_id, 'form')]
            action['res_id'] = delivery_ids and delivery_ids[0] or False
        return action

    @api.multi
    def action_view_return(self):
        '''
        该销货订单对应的退货单
        '''
        self.ensure_one()
        action = {
            'name': u'销售退货单',
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'sell.delivery',
            'view_id': False,
            'target': 'current',
        }
        tree_view_id = self.env.ref('sell.sell_return_tree').id
        form_view_id = self.env.ref('sell.sell_return_form').id
        delivery_ids = [delivery.id for delivery in self.delivery_ids if delivery.is_return]
        if len(delivery_ids) > 1:
            action['domain'] = "[('id','in',[" + \
                               ','.join(map(str, delivery_ids)) + "])]"
            action['view_mode'] = 'tree,form'
            action['views'] = [(tree_view_id, 'tree'), (form_view_id, 'form')]
        elif len(delivery_ids) == 1:
            action['views'] = [(form_view_id, 'form')]
            action['res_id'] = delivery_ids and delivery_ids[0] or False
        return action
예제 #6
0
class SaleOrderLine(models.Model):
    _inherit = "sale.order.line"

    @api.depends('is_changed', 'product_uom_qty', 'discount', 'price_unit',
                 'tax_id', 'volume', 'order_id.formula_type')
    def _compute_amount(self):
        """
        Compute the amounts of the SO line.
        """
        for line in self:
            final_qty = line.product_uom_qty
            price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)

            if line.order_id.formula_type == "Volume":
                final_qty = line.volume_qty

            taxes = line.tax_id.compute_all(
                price,
                line.order_id.currency_id,
                final_qty,
                product=line.product_id,
                partner=line.order_id.partner_shipping_id)
            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'],
            })

    @api.multi
    def _get_default_name(self):
        # return "Apasih"
        return self.product_id.name

    is_changed = fields.Boolean('Changed')
    is_qty_volume = fields.Boolean('Qty Volume')
    marking = fields.Char('No. Marking')
    marking_id = fields.Many2one('pwk.marking', 'Marking Image')
    actual_size = fields.Float('Actual Size')
    product_uom_qty = fields.Float(string='PCS',
                                   digits=dp.get_precision('ZeroDecimal'))
    thick = fields.Float(compute="_get_size",
                         string='Thick',
                         digits=dp.get_precision('OneDecimal'))
    width = fields.Float(compute="_get_size",
                         string='Width',
                         digits=dp.get_precision('ZeroDecimal'))
    length = fields.Float(compute="_get_size",
                          string='Length',
                          digits=dp.get_precision('ZeroDecimal'))
    volume_qty = fields.Float('Qty (Volume)',
                              digits=dp.get_precision('FourDecimal'))
    container_ids = fields.One2many('sale.order.line.container', 'reference',
                                    'Container')
    stempel_id = fields.Many2one('pwk.stempel', 'Stempel')
    sticker_id = fields.Many2one('pwk.sticker', 'Sticker')
    stempel_position = fields.Selection([('Edge', 'Edge'), ('Back', 'Back'),
                                         ('Edge and Back', 'Edge and Back')],
                                        string="Position",
                                        default="Edge")
    name = fields.Text(string='Description',
                       required=True,
                       default=_get_default_name)

    sale_po_number = fields.Char(related='order_id.po_number',
                                 string='PO Number')
    sale_date_order = fields.Date(related='order_id.date_order',
                                  string='Date Order')
    sale_partner_id = fields.Many2one(related='order_id.partner_id',
                                      comodel_name='res.partner',
                                      string='Customer')
    outstanding_order_pcs = fields.Float(compute="_get_outstanding_order_pcs",
                                         string="Outstanding Order")
    total_crate_qty = fields.Float(compute="_get_total_crate_qty",
                                   string="Total Qty Crates")

    crate_number = fields.Integer('Crate Number')
    crate_qty_each = fields.Integer('Crate Qty each')
    crate_qty_total = fields.Integer('Crate Total')
    crate_position_id = fields.Many2one('pwk.position', 'Crate Position')
    crate_pallet_id = fields.Many2one('pwk.pallet', 'Crate Pallet')
    crate_strapping_id = fields.Many2one('pwk.strapping', 'Crate Strapping')

    auto_volume = fields.Float(compute="_get_volume_qty",
                               string='Volume',
                               digits=dp.get_precision('FourDecimal'))
    volume = fields.Float(compute="_get_volume_qty",
                          string='Volume',
                          digits=dp.get_precision('FourDecimal'))

    @api.depends('thick', 'width', 'length', 'product_uom_qty', 'product_id',
                 'is_qty_volume')
    def _get_volume_qty(self):
        for res in self:
            res.volume = ((res.product_uom_qty * res.width * res.length *
                           res.thick)) / 1000000000
            res.auto_volume = res.volume

    @api.depends('container_ids.qty')
    def _get_total_crate_qty(self):
        for res in self:
            total_crate_qty = 0
            if res.container_ids:
                for container in res.container_ids:
                    total_crate_qty += container.qty

            res.total_crate_qty = total_crate_qty

    @api.depends('product_uom_qty')
    def _get_outstanding_order_pcs(self):
        for res in self:
            outstanding_order_pcs = res.product_uom_qty

            rpb_line_ids = self.env['pwk.rpb.line'].search([
                ('product_id', '=', res.product_id.id),
                ('sale_line_id', '=', res.id)
            ])

            if rpb_line_ids:
                for line in rpb_line_ids:
                    outstanding_order_pcs -= line.subtotal_qty

            res.outstanding_order_pcs = outstanding_order_pcs

    @api.depends('product_id')
    def _get_size(self):
        for res in self:
            thick = 0
            width = 0
            length = 0

            if res.product_id:
                thick = res.product_id.tebal
                width = res.product_id.lebar
                length = res.product_id.panjang

            res.thick = thick
            res.width = width
            res.length = length

    @api.onchange('product_uom_qty', 'product_uom', 'route_id')
    def _onchange_product_id_check_availability(self):
        if not self.product_id or not self.product_uom_qty or not self.product_uom:
            self.product_packaging = False
            return {}
        if self.product_id.type == 'product':
            precision = self.env['decimal.precision'].precision_get(
                'Product Unit of Measure')
            product = self.product_id.with_context(
                warehouse=self.order_id.warehouse_id.id,
                lang=self.order_id.partner_id.lang or self.env.user.lang
                or 'en_US')
            product_qty = self.product_uom._compute_quantity(
                self.product_uom_qty, self.product_id.uom_id)
            if float_compare(product.virtual_available,
                             product_qty,
                             precision_digits=precision) == -1:
                is_available = self._check_routing()
                if not is_available:
                    print("Nothing to do")
                    # message =  _('You plan to sell %s %s of %s but you only have %s %s available in %s warehouse.') % \
                    #         (self.product_uom_qty, self.product_uom.name, self.product_id.name, product.virtual_available, product.uom_id.name, self.order_id.warehouse_id.name)
                    # # We check if some products are available in other warehouses.
                    # if float_compare(product.virtual_available, self.product_id.virtual_available, precision_digits=precision) == -1:
                    #     message += _('\nThere are %s %s available across all warehouses.\n\n') % \
                    #             (self.product_id.virtual_available, product.uom_id.name)
                    #     for warehouse in self.env['stock.warehouse'].search([]):
                    #         quantity = self.product_id.with_context(warehouse=warehouse.id).virtual_available
                    #         if quantity > 0:
                    #             message += "%s: %s %s\n" % (warehouse.name, quantity, self.product_id.uom_id.name)
                    # warning_mess = {
                    #     'title': _('Not enough inventory!'),
                    #     'message' : message
                    # }
                    # return {'warning': warning_mess}
        return {}

    @api.onchange('product_id', 'product_uom_qty', 'product_uom')
    def product_id_change(self):
        if not self.product_id:
            return {'domain': {'product_uom': []}}

        # remove the is_custom values that don't belong to this template
        # for pacv in self.product_custom_attribute_value_ids:
        #     if pacv.attribute_value_id not in self.product_id.product_tmpl_id._get_valid_product_attribute_values():
        #         self.product_custom_attribute_value_ids -= pacv

        # remove the no_variant attributes that don't belong to this template
        # for ptav in self.product_no_variant_attribute_value_ids:
        #     if ptav.product_attribute_value_id not in self.product_id.product_tmpl_id._get_valid_product_attribute_values():
        #         self.product_no_variant_attribute_value_ids -= ptav

        vals = {}
        domain = {
            'product_uom':
            [('category_id', '=', self.product_id.uom_id.category_id.id)]
        }
        if not self.product_uom or (self.product_id.uom_id.id !=
                                    self.product_uom.id):
            vals['product_uom'] = self.product_id.uom_id
            vals['product_uom_qty'] = self.product_uom_qty or 1.0

        product = self.product_id.with_context(
            lang=self.order_id.partner_id.lang,
            partner=self.order_id.partner_id,
            quantity=vals.get('product_uom_qty') or self.product_uom_qty,
            date=self.order_id.date_order,
            pricelist=self.order_id.pricelist_id.id,
            uom=self.product_uom.id)

        result = {'domain': domain}

        # name = self.get_sale_order_line_multiline_description_sale(product)
        name = product.name
        vals.update(name=name)

        self._compute_tax_id()

        if self.order_id.pricelist_id and self.order_id.partner_id:
            vals['price_unit'] = self.env[
                'account.tax']._fix_tax_included_price_company(
                    self._get_display_price(product), product.taxes_id,
                    self.tax_id, self.company_id)
        self.update(vals)

        title = False
        message = False
        warning = {}
        if product.sale_line_warn != 'no-message':
            title = _("Warning for %s") % product.name
            message = product.sale_line_warn_msg
            warning['title'] = title
            warning['message'] = message
            result = {'warning': warning}
            if product.sale_line_warn == 'block':
                self.product_id = False

        return result
예제 #7
0
파일: hr_expense.py 프로젝트: westlyou/odoo
class HrExpense(models.Model):

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

    name = fields.Char(string='Expense Description',
                       readonly=True,
                       required=True,
                       states={
                           'draft': [('readonly', False)],
                           'refused': [('readonly', False)]
                       })
    date = fields.Date(readonly=True,
                       states={
                           'draft': [('readonly', False)],
                           'refused': [('readonly', False)]
                       },
                       default=fields.Date.context_today,
                       string="Expense Date")
    employee_id = fields.Many2one(
        'hr.employee',
        string="Employee",
        required=True,
        readonly=True,
        states={
            'draft': [('readonly', False)],
            'refused': [('readonly', False)]
        },
        default=lambda self: self.env['hr.employee'].search(
            [('user_id', '=', self.env.uid)], limit=1))
    product_id = fields.Many2one('product.product',
                                 string='Product',
                                 readonly=True,
                                 states={
                                     'draft': [('readonly', False)],
                                     'refused': [('readonly', False)]
                                 },
                                 domain=[('can_be_expensed', '=', True)],
                                 required=True)
    product_uom_id = fields.Many2one(
        'product.uom',
        string='Unit of Measure',
        required=True,
        readonly=True,
        states={
            'draft': [('readonly', False)],
            'refused': [('readonly', False)]
        },
        default=lambda self: self.env['product.uom'].search(
            [], limit=1, order='id'))
    unit_amount = fields.Float(string='Unit Price',
                               readonly=True,
                               required=True,
                               states={
                                   'draft': [('readonly', False)],
                                   'refused': [('readonly', False)]
                               },
                               digits=dp.get_precision('Product Price'))
    quantity = fields.Float(required=True,
                            readonly=True,
                            states={
                                'draft': [('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(string='Subtotal',
                                  store=True,
                                  compute='_compute_amount',
                                  digits=dp.get_precision('Account'))
    total_amount = fields.Float(string='Total',
                                store=True,
                                compute='_compute_amount',
                                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')
    account_id = fields.Many2one(
        'account.account',
        string='Account',
        states={
            'post': [('readonly', True)],
            'done': [('readonly', True)]
        },
        default=lambda self: self.env['ir.property'].get(
            'property_account_expense_categ_id', 'product.category'))
    description = fields.Text()
    payment_mode = fields.Selection(
        [("own_account", "Employee (to reimburse)"),
         ("company_account", "Company")],
        default='own_account',
        states={
            'done': [('readonly', True)],
            'post': [('readonly', True)]
        },
        string="Payment By")
    attachment_number = fields.Integer(compute='_compute_attachment_number',
                                       string='Number of Attachments')
    state = fields.Selection([('draft', 'To Submit'), ('reported', 'Reported'),
                              ('done', 'Posted'), ('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(string="Bill Reference")

    @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:
                expense.state = "draft"
            elif expense.sheet_id.state == "cancel":
                expense.state = "refused"
            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.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'
                  ))

    @api.multi
    def 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 submit_expenses(self):
        if any(expense.state != 'draft' 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!"
                  ))
        return {
            'type': 'ir.actions.act_window',
            'view_mode': 'form',
            'res_model': 'hr.expense.sheet',
            'target': 'current',
            'context': {
                'default_expense_line_ids': [line.id for line in self],
                'default_employee_id': self[0].employee_id.id,
                'default_name': self[0].name if len(self.ids) == 1 else ''
            }
        }

    def _prepare_move_line(self, line):
        '''
        This function prepares move line of account.move related to an expense
        '''
        partner_id = self.employee_id.address_home_id.commercial_partner_id.id
        return {
            'date_maturity':
            line.get('date_maturity'),
            'partner_id':
            partner_id,
            'name':
            line['name'][:64],
            'debit':
            line['price'] > 0 and line['price'],
            'credit':
            line['price'] < 0 and -line['price'],
            'account_id':
            line['account_id'],
            'analytic_line_ids':
            line.get('analytic_line_ids'),
            'amount_currency':
            line['price'] > 0 and abs(line.get('amount_currency'))
            or -abs(line.get('amount_currency')),
            'currency_id':
            line.get('currency_id'),
            'tax_line_id':
            line.get('tax_line_id'),
            'tax_ids':
            line.get('tax_ids'),
            'quantity':
            line.get('quantity', 1.00),
            'product_id':
            line.get('product_id'),
            'product_uom_id':
            line.get('uom_id'),
            'analytic_account_id':
            line.get('analytic_account_id'),
            'payment_id':
            line.get('payment_id'),
        }

    @api.multi
    def _compute_expense_totals(self, company_currency, account_move_lines,
                                move_date):
        '''
        internal method used for computation of total amount of an expense in the company currency and
        in the expense currency, given the account_move_lines that will be created. It also do some small
        transformations at these account_move_lines (for multi-currency purposes)

        :param account_move_lines: list of dict
        :rtype: tuple of 3 elements (a, b ,c)
            a: total in company currency
            b: total in hr.expense currency
            c: account_move_lines potentially modified
        '''
        self.ensure_one()
        total = 0.0
        total_currency = 0.0
        for line in account_move_lines:
            line['currency_id'] = False
            line['amount_currency'] = False
            if self.currency_id != company_currency:
                line['currency_id'] = self.currency_id.id
                line['amount_currency'] = line['price']
                line['price'] = self.currency_id.with_context(
                    date=move_date or fields.Date.context_today(self)).compute(
                        line['price'], company_currency)
            total -= line['price']
            total_currency -= line['amount_currency'] or line['price']
        return total, total_currency, account_move_lines

    @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 = {}
        for expense in self:
            journal = expense.sheet_id.bank_journal_id if expense.payment_mode == 'company_account' else expense.sheet_id.journal_id
            #create the move that will contain the accounting entries
            acc_date = expense.sheet_id.accounting_date or expense.date
            if not expense.sheet_id.id in move_group_by_sheet:
                move = self.env['account.move'].create({
                    'journal_id':
                    journal.id,
                    'company_id':
                    self.env.user.company_id.id,
                    'date':
                    acc_date,
                    'ref':
                    expense.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':
                    '/',
                })
                move_group_by_sheet[expense.sheet_id.id] = move
            else:
                move = move_group_by_sheet[expense.sheet_id.id]
            company_currency = expense.company_id.currency_id
            diff_currency_p = expense.currency_id != company_currency
            #one account.move.line per expense (+taxes..)
            move_lines = expense._move_line_get()

            #create one more move line, a counterline for the total on payable account
            payment_id = False
            total, total_currency, move_lines = expense._compute_expense_totals(
                company_currency, move_lines, acc_date)
            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))
                emp_account = expense.sheet_id.bank_journal_id.default_credit_account_id.id
                journal = expense.sheet_id.bank_journal_id
                #create payment
                payment_methods = (
                    total < 0
                ) and journal.outbound_payment_method_ids or 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':
                    total < 0 and 'outbound' or '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':
                    diff_currency_p and expense.currency_id.id
                    or journal_currency.id,
                    'amount':
                    diff_currency_p and abs(total_currency) or abs(total),
                    'name':
                    expense.name,
                })
                payment_id = payment.id
            else:
                if not expense.employee_id.address_home_id:
                    raise UserError(
                        _("No Home Address found for the employee %s, please configure one."
                          ) % (expense.employee_id.name))
                emp_account = expense.employee_id.address_home_id.property_account_payable_id.id

            aml_name = expense.employee_id.name + ': ' + expense.name.split(
                '\n')[0][:64]
            move_lines.append({
                'type':
                'dest',
                'name':
                aml_name,
                'price':
                total,
                'account_id':
                emp_account,
                'date_maturity':
                acc_date,
                'amount_currency':
                diff_currency_p and total_currency or False,
                'currency_id':
                diff_currency_p and expense.currency_id.id or False,
                'payment_id':
                payment_id,
            })

            #convert eml into an osv-valid format
            lines = [(0, 0, expense._prepare_move_line(x)) for x in move_lines]
            move.with_context(dont_create_taxes=True).write(
                {'line_ids': lines})
            expense.sheet_id.write({'account_move_id': move.id})
            if expense.payment_mode == 'company_account':
                expense.sheet_id.paid_expense_sheets()
        for move in pycompat.values(move_group_by_sheet):
            move.post()
        return True

    @api.multi
    def _prepare_move_line_value(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`.'
                      ))
        aml_name = self.employee_id.name + ': ' + self.name.split('\n')[0][:64]
        move_line = {
            'type': 'src',
            'name': aml_name,
            'price_unit': self.unit_amount,
            'quantity': self.quantity,
            'price': self.total_amount,
            'account_id': account.id,
            'product_id': self.product_id.id,
            'uom_id': self.product_uom_id.id,
            'analytic_account_id': self.analytic_account_id.id,
        }
        return move_line

    @api.multi
    def _move_line_get(self):
        account_move = []
        for expense in self:
            move_line = expense._prepare_move_line_value()
            account_move.append(move_line)

            # Calculate tax lines and adjust base line
            taxes = expense.tax_ids.compute_all(expense.unit_amount,
                                                expense.currency_id,
                                                expense.quantity,
                                                expense.product_id)
            account_move[-1]['price'] = taxes['total_excluded']
            account_move[-1]['tax_ids'] = [(6, 0, expense.tax_ids.ids)]
            for tax in taxes['taxes']:
                account_move.append({
                    'type':
                    'tax',
                    'name':
                    tax['name'],
                    'price_unit':
                    tax['amount'],
                    'quantity':
                    1,
                    'price':
                    tax['amount'],
                    'account_id':
                    tax['account_id'] or move_line['account_id'],
                    'tax_line_id':
                    tax['id'],
                })
        return account_move

    @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

    @api.model
    def get_empty_list_help(self, help_message):
        if 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="oe_view_nocontent_create">%s<br/>%s</p>%s' % (
                    _('Click to add a new expense,'),
                    _('or send receipts by email to %s.') %
                    (link, ), help_message)
        return super(HrExpense, self).get_empty_list_help(help_message)

    @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)

        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(), '')
            product = self.env['product.product'].search([
                ('default_code', 'ilike', product_code.group(1))
            ]) or default_product

        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,
            'quantity': 1,
            'unit_amount': price,
            'company_id': employee.company_id.id,
        })
        return super(HrExpense, self).message_new(msg_dict, custom_values)
예제 #8
0
class supplier_statements_report(models.Model):
    _name = "supplier.statements.report"
    _description = u"供应商对账单"
    _auto = False
    _order = 'id, date'

    @api.one
    @api.depends('amount', 'pay_amount', 'partner_id')
    def _compute_balance_amount(self):
        pre_record = self.search([
            ('id', '=', self.id - 1),
            ('partner_id', '=', self.partner_id.id)
        ])
        # 相邻的两条记录,partner不同,应付款余额要清零并重新计算
        if pre_record:
            before_balance = pre_record.this_balance_amount
        else:
            before_balance = 0
        self.balance_amount += before_balance + self.amount - self.pay_amount + self.discount_money
        self.this_balance_amount = self.balance_amount

    partner_id = fields.Many2one('partner', string=u'业务伙伴', readonly=True)
    name = fields.Char(string=u'单据编号', readonly=True)
    date = fields.Date(string=u'单据日期', readonly=True)
    done_date = fields.Datetime(string=u'完成日期', readonly=True)
    purchase_amount = fields.Float(string=u'采购金额', readonly=True,
                                   digits=dp.get_precision('Amount'))
    benefit_amount = fields.Float(string=u'优惠金额', readonly=True,
                                  digits=dp.get_precision('Amount'))
    amount = fields.Float(string=u'应付金额', readonly=True,
                          digits=dp.get_precision('Amount'))
    pay_amount = fields.Float(string=u'实际付款金额', readonly=True,
                              digits=dp.get_precision('Amount'))
    discount_money = fields.Float(string=u'付款折扣', readonly=True,
                                  digits=dp.get_precision('Amount'))
    balance_amount = fields.Float(
        string=u'应付款余额',
        compute='_compute_balance_amount',
        readonly=True,
        digits=dp.get_precision('Amount'))
    this_balance_amount = fields.Float(string=u'应付款余额',
                                       digits=dp.get_precision('Amount'))
    note = fields.Char(string=u'备注', readonly=True)
    move_id = fields.Many2one('wh.move', string=u'出入库单', readonly=True)

    def init(self):
        # union money_order(type = 'pay'), money_invoice(type = 'expense')
        cr = self._cr
        tools.drop_view_if_exists(cr, 'supplier_statements_report')
        cr.execute("""
            CREATE or REPLACE VIEW supplier_statements_report AS (
            SELECT  ROW_NUMBER() OVER(ORDER BY partner_id, date, amount desc) AS id,
                    partner_id,
                    name,
                    date,
                    done_date,
                    purchase_amount,
                    benefit_amount,
                    amount,
                    pay_amount,
                    discount_money,
                    balance_amount,
                    0 AS this_balance_amount,
                    note,
                    move_id
            FROM
                (
                SELECT m.partner_id,
                        m.name,
                        m.date,
                        m.write_date AS done_date,
                        0 AS purchase_amount,
                        0 AS benefit_amount,
                        0 AS amount,
                        m.amount AS pay_amount,
                        m.discount_amount AS discount_money,
                        0 AS balance_amount,
                        m.note,
                        NULL AS move_id
                FROM money_order AS m
                WHERE m.type = 'pay' AND m.state = 'done'
                UNION ALL
                SELECT  mi.partner_id,
                        mi.name,
                        mi.date,
                        mi.create_date AS done_date,
                        br.amount + br.discount_amount AS purchase_amount,
                        br.discount_amount AS benefit_amount,
                        mi.amount,
                        0 AS pay_amount,
                        0 AS discount_money,
                        0 AS balance_amount,
                        Null AS note,
                        mi.move_id
                FROM money_invoice AS mi
                LEFT JOIN core_category AS c ON mi.category_id = c.id
                LEFT JOIN buy_receipt AS br ON br.buy_move_id = mi.move_id
                WHERE c.type = 'expense' AND mi.state = 'done'
                UNION ALL
                SELECT  ro.partner_id,
                        ro.name,
                        ro.date,
                        ro.write_date AS done_date,
                        0 AS purchase_amount,
                        0 AS benefit_amount,
                        0 AS amount,
                        sol.this_reconcile AS pay_amount,
                        0 AS discount_money,
                        0 AS balance_amount,
                        Null AS note,
                        0 AS move_id
                FROM reconcile_order AS ro
                LEFT JOIN money_invoice AS mi ON mi.name = ro.name
                LEFT JOIN source_order_line AS sol ON sol.payable_reconcile_id = ro.id
                WHERE ro.state = 'done' AND mi.state = 'done' AND mi.name ilike 'RO%'
                ) AS ps)
        """)

    @api.multi
    def find_source_order(self):
        # 查看原始单据,三情况:收付款单、采购退货单、采购入库单、核销单
        self.ensure_one()
        model_view = {
            'money.order': {'name': u'付款单',
                            'view': 'money.money_order_form'},
            'buy.receipt': {'name': u'采购入库单',
                            'view': 'buy.buy_receipt_form',
                            'name_return': u'采购退货单',
                            'view_return': 'buy.buy_return_form'},
            'reconcile.order': {'name': u'核销单',
                                'view': 'money.reconcile_order_form'}
        }
        for model, view_dict in model_view.iteritems():
            res = self.env[model].search([('name', '=', self.name)])
            name = model == 'buy.receipt' and res.is_return and \
                   view_dict['name_return'] or view_dict['name']
            view = model == 'buy.receipt' and res.is_return and \
                   self.env.ref(view_dict['view_return']) \
                   or self.env.ref(view_dict['view'])
            if res:
                return {
                    'name': name,
                    'view_mode': 'form',
                    'view_id': False,
                    'views': [(view.id, 'form')],
                    'res_model': model,
                    'type': 'ir.actions.act_window',
                    'res_id': res.id,
                }
        raise UserError(u'期初余额没有原始单据可供查看。')
예제 #9
0
class account_invoice(models.Model):
    _inherit = 'account.invoice'
    dosar_id = fields.Many2one('helpan.dosar', 'Dosar')
    exchange_rate = fields.Float(
        'Curs Euro', digits=dp.get_precision('Account')
    )
예제 #10
0
파일: models.py 프로젝트: mesiboku/omni_gh
class salesinvdp(models.Model):
    _inherit = 'account.invoice.line'

    quantity = fields.Float(digits=dp.get_precision('Sale Order Quantity'))
예제 #11
0
파일: models.py 프로젝트: mesiboku/omni_gh
class salesdp(models.Model):
    _inherit = 'sale.order.line'

    product_uom_qty = fields.Float(
        digits=dp.get_precision('Sale Order Quantity'))
    qty_invoiced = fields.Float(digits=dp.get_precision('Sale Order Quantity'))
예제 #12
0
class AccountAssetLine(models.Model):
    _name = 'account.asset.line'
    _description = 'Asset depreciation table line'
    _order = 'type, line_date'

    name = fields.Char(string='Depreciation Name', size=64, readonly=True)
    asset_id = fields.Many2one(comodel_name='account.asset',
                               string='Asset',
                               required=True,
                               ondelete='cascade')
    previous_id = fields.Many2one(comodel_name='account.asset.line',
                                  string='Previous Depreciation Line',
                                  readonly=True)
    parent_state = fields.Selection(
        related='asset_id.state',
        string='State of Asset',
        readonly=True,
    )
    depreciation_base = fields.Float(
        related='asset_id.depreciation_base',
        string='Depreciation Base',
        readonly=True,
    )
    amount = fields.Float(string='Amount',
                          digits=dp.get_precision('Account'),
                          required=True)
    remaining_value = fields.Float(compute='_compute_values',
                                   digits=dp.get_precision('Account'),
                                   string='Next Period Depreciation',
                                   store=True)
    depreciated_value = fields.Float(compute='_compute_values',
                                     digits=dp.get_precision('Account'),
                                     string='Amount Already Depreciated',
                                     store=True)
    line_date = fields.Date(string='Date', required=True)
    line_days = fields.Integer(
        string='Days',
        readonly=True,
    )
    move_id = fields.Many2one(comodel_name='account.move',
                              string='Depreciation Entry',
                              readonly=True)
    move_check = fields.Boolean(compute='_compute_move_check',
                                string='Posted',
                                store=True)
    type = fields.Selection(selection=[('create', 'Depreciation Base'),
                                       ('depreciate', 'Depreciation'),
                                       ('remove', 'Asset Removal')],
                            readonly=True,
                            default='depreciate')
    init_entry = fields.Boolean(
        string='Initial Balance Entry',
        help="Set this flag for entries of previous fiscal years "
        "for which Odoo has not generated accounting entries.")

    @api.depends('amount', 'previous_id', 'type')
    @api.multi
    def _compute_values(self):
        dlines = self
        if self.env.context.get('no_compute_asset_line_ids'):
            # skip compute for lines in unlink
            exclude_ids = self.env.context['no_compute_asset_line_ids']
            dlines = self.filtered(lambda l: l.id not in exclude_ids)
        dlines = dlines.filtered(lambda l: l.type == 'depreciate')
        dlines = dlines.sorted(key=lambda l: l.line_date)

        # Group depreciation lines per asset
        asset_ids = dlines.mapped('asset_id')
        grouped_dlines = []
        for asset in asset_ids:
            grouped_dlines.append(
                dlines.filtered(lambda l: l.asset_id.id == asset.id))

        for dlines in grouped_dlines:
            for i, dl in enumerate(dlines):
                if i == 0:
                    depreciation_base = dl.depreciation_base
                    tmp = depreciation_base - dl.previous_id.remaining_value
                    depreciated_value = dl.previous_id and tmp or 0.0
                    remaining_value = \
                        depreciation_base - depreciated_value - dl.amount
                else:
                    depreciated_value += dl.previous_id.amount
                    remaining_value -= dl.amount
                dl.depreciated_value = depreciated_value
                dl.remaining_value = remaining_value

    @api.depends('move_id')
    @api.multi
    def _compute_move_check(self):
        for line in self:
            line.move_check = bool(line.move_id)

    @api.onchange('amount')
    def _onchange_amount(self):
        if self.type == 'depreciate':
            self.remaining_value = self.depreciation_base - \
                self.depreciated_value - self.amount

    @api.multi
    def write(self, vals):
        for dl in self:
            line_date = vals.get('line_date') or dl.line_date
            asset_lines = dl.asset_id.depreciation_line_ids
            if list(vals.keys()) == ['move_id'] and not vals['move_id']:
                # allow to remove an accounting entry via the
                # 'Delete Move' button on the depreciation lines.
                if not self.env.context.get('unlink_from_asset'):
                    raise UserError(
                        _("You are not allowed to remove an accounting entry "
                          "linked to an asset."
                          "\nYou should remove such entries from the asset."))
            elif list(vals.keys()) == ['asset_id']:
                continue
            elif dl.move_id and not self.env.context.get(
                    'allow_asset_line_update') and dl.type != 'create':
                raise UserError(
                    _("You cannot change a depreciation line "
                      "with an associated accounting entry."))
            elif vals.get('init_entry'):
                check = asset_lines.filtered(
                    lambda l: l.move_check and l.type == 'depreciate' and l.
                    line_date <= line_date)
                if check:
                    raise UserError(
                        _("You cannot set the 'Initial Balance Entry' flag "
                          "on a depreciation line "
                          "with prior posted entries."))
            elif vals.get('line_date'):
                if dl.type == 'create':
                    check = asset_lines.filtered(
                        lambda l: l.type != 'create' and
                        (l.init_entry or l.move_check) and l.line_date < fields
                        .Date.to_date(vals['line_date']))
                    if check:
                        raise UserError(
                            _("You cannot set the Asset Start Date "
                              "after already posted entries."))
                else:
                    check = asset_lines.filtered(lambda l: l != dl and (
                        l.init_entry or l.move_check
                    ) and l.line_date > fields.Date.to_date(vals['line_date']))
                    if check:
                        raise UserError(
                            _("You cannot set the date on a depreciation line "
                              "prior to already posted entries."))
        return super().write(vals)

    @api.multi
    def unlink(self):
        for dl in self:
            if dl.type == 'create' and dl.amount:
                raise UserError(
                    _("You cannot remove an asset line "
                      "of type 'Depreciation Base'."))
            elif dl.move_id:
                raise UserError(
                    _("You cannot delete a depreciation line with "
                      "an associated accounting entry."))
            previous = dl.previous_id
            next_line = dl.asset_id.depreciation_line_ids.filtered(
                lambda l: l.previous_id == dl and l not in self)
            if next_line:
                next_line.previous_id = previous
        return super(
            AccountAssetLine,
            self.with_context(no_compute_asset_line_ids=self.ids)).unlink()

    def _setup_move_data(self, depreciation_date):
        asset = self.asset_id
        move_data = {
            'name': asset.name,
            'date': depreciation_date,
            'ref': self.name,
            'journal_id': asset.profile_id.journal_id.id,
        }
        return move_data

    def _setup_move_line_data(self, depreciation_date, account, ml_type, move):
        """Prepare data to be propagated to account.move.line"""
        asset = self.asset_id
        amount = self.amount
        analytic_id = False
        analytic_tags = self.env["account.analytic.tag"]
        if ml_type == 'depreciation':
            debit = amount < 0 and -amount or 0.0
            credit = amount > 0 and amount or 0.0
        elif ml_type == 'expense':
            debit = amount > 0 and amount or 0.0
            credit = amount < 0 and -amount or 0.0
            analytic_id = asset.account_analytic_id.id
            analytic_tags = asset.analytic_tag_ids
        move_line_data = {
            'name': asset.name,
            'ref': self.name,
            'move_id': move.id,
            'account_id': account.id,
            'credit': credit,
            'debit': debit,
            'journal_id': asset.profile_id.journal_id.id,
            'partner_id': asset.partner_id.id,
            'analytic_account_id': analytic_id,
            'analytic_tag_ids': [(4, tag.id) for tag in analytic_tags],
            'date': depreciation_date,
            'asset_id': asset.id,
        }
        return move_line_data

    @api.multi
    def create_move(self):
        created_move_ids = []
        asset_ids = set()
        ctx = dict(self.env.context,
                   allow_asset=True,
                   check_move_validity=False)
        for line in self:
            asset = line.asset_id
            depreciation_date = line.line_date
            am_vals = line._setup_move_data(depreciation_date)
            move = self.env['account.move'].with_context(ctx).create(am_vals)
            depr_acc = asset.profile_id.account_depreciation_id
            exp_acc = asset.profile_id.account_expense_depreciation_id
            aml_d_vals = line._setup_move_line_data(depreciation_date,
                                                    depr_acc, 'depreciation',
                                                    move)
            self.env['account.move.line'].with_context(ctx).create(aml_d_vals)
            aml_e_vals = line._setup_move_line_data(depreciation_date, exp_acc,
                                                    'expense', move)
            self.env['account.move.line'].with_context(ctx).create(aml_e_vals)
            move.post()
            line.with_context(allow_asset_line_update=True).write(
                {'move_id': move.id})
            created_move_ids.append(move.id)
            asset_ids.add(asset.id)
        # we re-evaluate the assets to determine if we can close them
        for asset in self.env['account.asset'].browse(list(asset_ids)):
            if asset.company_currency_id.is_zero(asset.value_residual):
                asset.state = 'close'
        return created_move_ids

    @api.multi
    def open_move(self):
        self.ensure_one()
        return {
            'name': _("Journal Entry"),
            'view_type': 'form',
            'view_mode': 'tree,form',
            'res_model': 'account.move',
            'view_id': False,
            'type': 'ir.actions.act_window',
            'context': self.env.context,
            'domain': [('id', '=', self.move_id.id)],
        }

    @api.multi
    def unlink_move(self):
        for line in self:
            move = line.move_id
            if move.state == 'posted':
                move.button_cancel()
            move.with_context(unlink_from_asset=True).unlink()
            # trigger store function
            line.with_context(unlink_from_asset=True).write({'move_id': False})
            if line.parent_state == 'close':
                line.asset_id.write({'state': 'open'})
            elif line.parent_state == 'removed' and line.type == 'remove':
                line.asset_id.write({
                    'state': 'close',
                    'date_remove': False,
                })
                line.unlink()
        return True
예제 #13
0
class WhOut(models.Model):
    _name = 'wh.out'
    _description = u'其他出库单'
    _inherit = ['mail.thread']
    _order = 'date DESC, id DESC'

    _inherits = {
        'wh.move': 'move_id',
    }

    TYPE_SELECTION = [('inventory', u'盘亏'), ('others', u'其他出库'),
                      ('cancel', u'已作废')]

    move_id = fields.Many2one('wh.move',
                              u'移库单',
                              required=True,
                              index=True,
                              ondelete='cascade',
                              help=u'其他出库单对应的移库单')
    type = fields.Selection(TYPE_SELECTION,
                            u'业务类别',
                            default='others',
                            help=u'类别: 盘亏,其他出库')
    amount_total = fields.Float(compute='_get_amount_total',
                                string=u'合计成本金额',
                                store=True,
                                readonly=True,
                                digits=dp.get_precision('Amount'),
                                help=u'该出库单的出库金额总和')
    voucher_id = fields.Many2one('voucher',
                                 u'出库凭证',
                                 readonly=True,
                                 help=u'该出库单的后生成的出库凭证')

    @api.multi
    @inherits_after()
    def approve_order(self):
        for order in self:
            if order.state == 'done':
                raise UserError(u'请不要重复出库')
            voucher = order.create_voucher()
            order.write({
                'voucher_id': voucher and voucher[0] and voucher[0].id,
                'state': 'done',
            })
        return True

    @api.multi
    @inherits()
    def cancel_approved_order(self):
        for order in self:
            if order.state == 'draft':
                raise UserError(u'请不要重复撤销')
            order.delete_voucher()
            order.state = 'draft'
        return True

    @api.multi
    @inherits()
    def unlink(self):
        for order in self:
            return order.move_id.unlink()

    @api.one
    @api.depends('line_out_ids.cost')
    def _get_amount_total(self):
        self.amount_total = sum(line.cost for line in self.line_out_ids)

    def get_move_origin(self, vals):
        return self._name + '.' + vals.get('type')

    @api.model
    @create_name
    @create_origin
    def create(self, vals):
        return super(WhOut, self).create(vals)

    @api.multi
    @api.onchange('type')
    def onchange_type(self):
        self.warehouse_dest_id = self.env['warehouse'].get_warehouse_by_type(
            self.type)

    def goods_inventory(self, vals):
        """
        审核时若仓库中商品不足,则产生补货向导生成其他入库单并审核。
        :param vals: 创建其他入库单需要的字段及取值信息构成的字典
        :return:
        """
        auto_in = self.env['wh.in'].create(vals)
        self.with_context({
            'wh_in_line_ids': [line.id for line in auto_in.line_in_ids]
        }).approve_order()

    @api.one
    def create_voucher(self):
        '''
        其他出库单生成出库凭证
        借:如果出库类型为盘亏,取科目 1901 待处理财产损益;如果为其他,取核算类别的会计科目
        贷:库存商品(商品分类上会计科目)
        '''
        voucher = self.env['voucher'].create({
            'date':
            self.date,
            'ref':
            '%s,%s' % (self._name, self.id)
        })
        credit_sum = 0  # 贷方之和
        for line in self.line_out_ids:
            if line.cost:  # 贷方行(多行)
                self.env['voucher.line'].create({
                    'name':
                    u'%s %s' % (self.name, self.note or ''),
                    'account_id':
                    line.goods_id.category_id.account_id.id,
                    'credit':
                    line.cost,
                    'voucher_id':
                    voucher.id,
                    'goods_id':
                    line.goods_id.id,
                    'goods_qty':
                    line.goods_qty,
                })
            credit_sum += line.cost
        account = self.type == 'inventory' \
            and self.env.ref('finance.small_business_chart1901') \
            or self.finance_category_id.account_id
        if credit_sum:  # 借方行(汇总一行)
            self.env['voucher.line'].create({
                'name':
                u'%s %s' % (self.name, self.note or ''),
                'account_id':
                account.id,
                'debit':
                credit_sum,
                'voucher_id':
                voucher.id,
            })
        if len(voucher.line_ids) > 0:
            voucher.voucher_done()
            return voucher
        else:
            voucher.unlink()

    @api.one
    def delete_voucher(self):
        # 反审核其他出库单时删除对应的出库凭证
        voucher = self.voucher_id
        if voucher.state == 'done':
            voucher.voucher_draft()

        voucher.unlink()
예제 #14
0
class WhInternal(models.Model):
    _name = 'wh.internal'
    _description = u'内部调拨单'
    _inherit = ['mail.thread']
    _order = 'date DESC, id DESC'

    _inherits = {
        'wh.move': 'move_id',
    }

    move_id = fields.Many2one('wh.move',
                              u'移库单',
                              required=True,
                              index=True,
                              ondelete='cascade',
                              help=u'调拨单对应的移库单')
    amount_total = fields.Float(compute='_get_amount_total',
                                string=u'合计成本金额',
                                store=True,
                                readonly=True,
                                digits=dp.get_precision('Amount'),
                                help=u'该调拨单的出库金额总和')

    def goods_inventory(self, vals):
        """
        审核时若仓库中商品不足,则产生补货向导生成其他入库单并审核。
        :param vals: 创建其他入库单需要的字段及取值信息构成的字典
        :return:
        """
        auto_in = self.env['wh.in'].create(vals)
        self.with_context({
            'wh_in_line_ids': [line.id for line in auto_in.line_in_ids]
        }).approve_order()

    @api.multi
    @inherits()
    def approve_order(self):
        for order in self:
            if order.state == 'done':
                raise UserError(u'请不要重复入库')
            if self.env.user.company_id.is_enable_negative_stock:
                result_vals = self.env['wh.move'].create_zero_wh_in(
                    self, self._name)
                if result_vals:
                    return result_vals
            order.state = 'done'
        return True

    @api.multi
    @inherits()
    def cancel_approved_order(self):
        for order in self:
            if order.state == 'draft':
                raise UserError(u'请不要重复撤销')
            order.state = 'draft'
        return True

    @api.multi
    @inherits()
    def unlink(self):
        for order in self:
            return order.move_id.unlink()

    @api.one
    @api.depends('line_out_ids.cost')
    def _get_amount_total(self):
        self.amount_total = sum(line.cost for line in self.line_out_ids)

    @api.model
    @create_name
    @create_origin
    def create(self, vals):
        return super(WhInternal, self).create(vals)
예제 #15
0
class WhIn(models.Model):
    _name = 'wh.in'
    _description = u'其他入库单'
    _inherit = ['mail.thread']
    _order = 'date DESC, id DESC'

    _inherits = {
        'wh.move': 'move_id',
    }

    TYPE_SELECTION = [
        ('inventory', u'盘盈'),
        ('others', u'其他入库'),
    ]

    move_id = fields.Many2one('wh.move',
                              u'移库单',
                              required=True,
                              index=True,
                              ondelete='cascade',
                              help=u'其他入库单对应的移库单')
    type = fields.Selection(TYPE_SELECTION,
                            u'业务类别',
                            default='others',
                            help=u'类别: 盘盈,其他入库,初始')
    amount_total = fields.Float(compute='_get_amount_total',
                                string=u'合计成本金额',
                                store=True,
                                readonly=True,
                                digits=dp.get_precision('Amount'),
                                help=u'该入库单的入库金额总和')
    voucher_id = fields.Many2one('voucher',
                                 u'入库凭证',
                                 readonly=True,
                                 help=u'该入库单确认后生成的入库凭证')
    is_init = fields.Boolean(u'初始化单')

    @api.multi
    @inherits()
    def approve_order(self):
        for order in self:
            if order.state == 'done':
                raise UserError(u'请不要重复入库')
            voucher = order.create_voucher()
            order.write({
                'voucher_id': voucher and voucher[0] and voucher[0].id,
                'state': 'done',
            })
        return True

    @api.multi
    @inherits()
    def cancel_approved_order(self):
        for order in self:
            if order.state == 'draft':
                raise UserError(u'请不要重复撤销')
            order.delete_voucher()
            order.state = 'draft'
        return True

    @api.multi
    @inherits()
    def unlink(self):
        for order in self:
            return order.move_id.unlink()

    @api.one
    @api.depends('line_in_ids.cost')
    def _get_amount_total(self):
        self.amount_total = sum(line.cost for line in self.line_in_ids)

    def get_move_origin(self, vals):
        return self._name + '.' + vals.get('type')

    @api.model
    @create_name
    @create_origin
    def create(self, vals):
        return super(WhIn, self).create(vals)

    @api.multi
    @api.onchange('type')
    def onchange_type(self):
        self.warehouse_id = self.env['warehouse'].get_warehouse_by_type(
            self.type).id

    @api.one
    def create_voucher(self):
        # 入库单生成入库凭证
        '''
        借:商品分类对应的会计科目 一般是库存商品
        贷:如果入库类型为盘盈,取科目 1901 待处理财产损益(暂时写死)
        如果入库类型为其他,取收发类别的会计科目
        '''

        # 初始化单的话,先找是否有初始化凭证,没有则新建一个
        if self.is_init:
            vouch_id = self.env['voucher'].search([('is_init', '=', True)])
            if not vouch_id:
                vouch_id = self.env['voucher'].create({
                    'date':
                    self.date,
                    'is_init':
                    True,
                    'ref':
                    '%s,%s' % (self._name, self.id)
                })
        else:
            vouch_id = self.env['voucher'].create({
                'date':
                self.date,
                'ref':
                '%s,%s' % (self._name, self.id)
            })
        debit_sum = 0
        for line in self.line_in_ids:
            init_obj = self.is_init and 'init_warehouse - %s' % (self.id) or ''
            if line.cost:
                self.env['voucher.line'].create({
                    'name':
                    u'%s %s' % (self.name, self.note or ''),
                    'account_id':
                    line.goods_id.category_id.account_id.id,
                    'debit':
                    line.cost,
                    'voucher_id':
                    vouch_id.id,
                    'goods_id':
                    line.goods_id.id,
                    'goods_qty':
                    line.goods_qty,
                    'init_obj':
                    init_obj,
                })
            debit_sum += line.cost

        # 贷方科目: 如果是盘盈则取主营业务成本,否则取收发类别上的科目
        account = self.type == 'inventory' \
            and self.env.ref('finance.small_business_chart1901') \
            or self.finance_category_id.account_id

        if not self.is_init:
            if debit_sum:
                self.env['voucher.line'].create({
                    'name':
                    u'%s %s' % (self.name, self.note or ''),
                    'account_id':
                    account.id,
                    'credit':
                    debit_sum,
                    'voucher_id':
                    vouch_id.id,
                })
        if not self.is_init:
            if len(vouch_id.line_ids) > 0:
                vouch_id.voucher_done()
                return vouch_id
            else:
                vouch_id.unlink()

    @api.one
    def delete_voucher(self):
        # 反审核入库单时删除对应的入库凭证
        if self.voucher_id:
            if self.voucher_id.state == 'done':
                self.voucher_id.voucher_draft()
            voucher = self.voucher_id
            # 始初化单反审核只删除明细行
            if self.is_init:
                vouch_obj = self.env['voucher'].search([('id', '=', voucher.id)
                                                        ])
                vouch_obj_lines = self.env['voucher.line'].search([
                    ('voucher_id', '=', vouch_obj.id),
                    ('goods_id', 'in',
                     [line.goods_id.id for line in self.line_in_ids]),
                    ('init_obj', '=', 'init_warehouse - %s' % (self.id)),
                ])
                for vouch_obj_line in vouch_obj_lines:
                    vouch_obj_line.unlink()
            else:
                voucher.unlink()
예제 #16
0
class ProductTemplate(models.Model):
    _inherit = 'product.template'

    # TODO this field should be move to PRC (product_replenishment_cost)
    replenishment_cost_last_update = fields.Datetime(
        'Replenishment Cost Last Update',
        track_visibility='onchange',
    )
    # TODO this field should be move to PRC (product_replenishment_cost)
    replenishment_base_cost = fields.Float(
        'Replenishment Base Cost',
        digits=dp.get_precision('Product Price'),
        track_visibility='onchange',
        help="Replanishment Cost expressed in 'Replenishment Base Cost "
        "Currency'.")
    replenishment_base_cost_currency_id = fields.Many2one(
        'res.currency',
        'Replenishment Base Cost Currency',
        auto_join=True,
        track_visibility='onchange',
        help="Currency used for the Replanishment Base Cost.")
    # TODO borrar, ya lo cambiamos en nustro modulo base de rep cost
    # lo que si dejamos es sobreescribir el metodo de computar, algo que
    # deberiamos mejorar tmb
    replenishment_cost = fields.Float(compute='_get_replenishment_cost', )
    # for now we make replenshiment cost field only on template and not in
    # product (this should be done in PRC)
    #     string='Replenishment Cost',
    #     store=False,
    #     digits=dp.get_precision('Product Price'),
    #     help="The cost that you have to support in order to produce or "
    #          "acquire the goods. Depending on the modules installed, "
    #          "this cost may be computed based on various pieces of "
    #          "information, for example Bills of Materials or latest "
    #          "Purchases."

    @api.model
    def cron_update_cost_from_replenishment_cost(self, limit=None):
        _logger.info('Running cron update cost from replenishment')
        return self.with_context(
            commit_transaction=True)._update_cost_from_replenishment_cost()

    @api.multi
    def _update_cost_from_replenishment_cost(self):
        """
        If we came from tree list, we update only in selected list
        """
        # hacemos search de nuevo por si se llama desde vista lista
        commit_transaction = self.env.context.get('commit_transaction')
        domain = [
            ('replenishment_base_cost', '!=', False),
            ('replenishment_base_cost_currency_id', '!=', False),
        ]
        if self:
            domain.append(('id', 'in', self.ids))

        batch_size = 1000
        product_ids = self.search(domain).ids
        sliced_product_ids = [
            product_ids[i:i + batch_size]
            for i in range(0, len(product_ids), batch_size)
        ]
        cr = self.env.cr
        run = 0
        for product_ids in sliced_product_ids:
            run += 1
            # hacemos invalidate cache para que no haga prefetch de todos,
            # solo los del slice
            self.invalidate_cache()
            recs = self.browse(product_ids)
            _logger.info(
                'Running update update cost for %s products. Run %s of %s' %
                (len(recs), run, len(sliced_product_ids)))
            for rec in recs:
                replenishment_cost = rec.replenishment_cost
                # TODO we should check if standar_price type is standar and
                # not by quants or similar, we remove that because it makes
                # it slower
                if not replenishment_cost:
                    continue
                rec.standard_price = replenishment_cost
                # we can not use sql because standar_price is a property,
                # perhups we can do it writing directly on the property but
                # we need to check if record exists, we can copy some code of
                # def set_multi
                # tal vez mejor que meter mano en esto hacer que solo se
                # actualicen los que se tienen que actualizar
                # seguramente en la v10 se mejoro el metodo de set de property
                # tmb
                # cr.execute(
                #     "UPDATE product_template SET standard_price=%s WHERE "
                #     "id=%s", (replenishment_cost, rec.id))

            # commit update (fo free memory?) also to have results stored
            # in the future, if we store the date, we can update only newones

            # principalmente agregamos esto por error en migracion pero tmb
            # para que solo se haga el commit por cron
            if commit_transaction:
                cr.commit()
            _logger.info('Finish updating cost of run %s' % run)

        return True

    @api.multi
    @api.constrains(
        'replenishment_base_cost',
        'replenishment_base_cost_currency_id',
    )
    def update_replenishment_cost_last_update(self):
        self.write({'replenishment_cost_last_update': fields.Datetime.now()})

    @api.multi
    # TODO ver si necesitamos borrar estos depends o no, por ahora
    # no parecen afectar performance y sirvern para que la interfaz haga
    # el onchange, pero no son fundamentales porque el campo no lo storeamos
    @api.depends(
        'currency_id',
        'replenishment_base_cost',
        # because of being stored
        'replenishment_base_cost_currency_id.rate_ids.rate',
        # and this if we change de date (name field)
        'replenishment_base_cost_currency_id.rate_ids.name',
    )
    def _get_replenishment_cost(self):
        _logger.info('Getting replenishment cost currency for ids %s' %
                     self.ids)
        for rec in self:
            rec.replenishment_cost = rec.get_replenishment_cost_currency(
                rec.replenishment_base_cost_currency_id,
                rec.currency_id,
                rec.replenishment_base_cost,
            )

    @api.model
    def get_replenishment_cost_currency(self, from_currency, to_currency,
                                        base_cost):
        replenishment_cost = False
        if from_currency and to_currency:
            replenishment_cost = base_cost
            if from_currency != to_currency:
                replenishment_cost = from_currency.compute(replenishment_cost,
                                                           to_currency,
                                                           round=False)
        return replenishment_cost
예제 #17
0
파일: product.py 프로젝트: zhuhh/odoo
class ProductTemplate(models.Model):
    _inherit = 'product.template'

    responsible_id = fields.Many2one(
        'res.users', string='Responsible', default=lambda self: self.env.uid,
        help="This user will be responsible of the next activities related to logistic operations for this product.")
    type = fields.Selection(selection_add=[('product', 'Storable Product')])
    property_stock_production = fields.Many2one(
        'stock.location', "Production Location",
        company_dependent=True, domain=[('usage', 'like', 'production')],
        help="This stock location will be used, instead of the default one, as the source location for stock moves generated by manufacturing orders.")
    property_stock_inventory = fields.Many2one(
        'stock.location', "Inventory Location",
        company_dependent=True, domain=[('usage', 'like', 'inventory')],
        help="This stock location will be used, instead of the default one, as the source location for stock moves generated when you do an inventory.")
    sale_delay = fields.Float(
        'Customer Lead Time', default=0,
        help="Delivery lead time, in days. It's the number of days, promised to the customer, between the confirmation of the sales order and the delivery.")
    tracking = fields.Selection([
        ('serial', 'By Unique Serial Number'),
        ('lot', 'By Lots'),
        ('none', 'No Tracking')], string="Tracking", help="Ensure the traceability of a storable product in your warehouse.", default='none', required=True)
    description_picking = fields.Text('Description on Picking', translate=True)
    description_pickingout = fields.Text('Description on Delivery Orders', translate=True)
    description_pickingin = fields.Text('Description on Receptions', translate=True)
    qty_available = fields.Float(
        'Quantity On Hand', compute='_compute_quantities', search='_search_qty_available',
        digits=dp.get_precision('Product Unit of Measure'))
    virtual_available = fields.Float(
        'Forecasted Quantity', compute='_compute_quantities', search='_search_virtual_available',
        digits=dp.get_precision('Product Unit of Measure'))
    incoming_qty = fields.Float(
        'Incoming', compute='_compute_quantities', search='_search_incoming_qty',
        digits=dp.get_precision('Product Unit of Measure'))
    outgoing_qty = fields.Float(
        'Outgoing', compute='_compute_quantities', search='_search_outgoing_qty',
        digits=dp.get_precision('Product Unit of Measure'))
    # The goal of these fields is not to be able to search a location_id/warehouse_id but
    # to properly make these fields "dummy": only used to put some keys in context from
    # the search view in order to influence computed field
    location_id = fields.Many2one('stock.location', 'Location', store=False, search=lambda operator, operand, vals: [])
    warehouse_id = fields.Many2one('stock.warehouse', 'Warehouse', store=False, search=lambda operator, operand, vals: [])
    route_ids = fields.Many2many(
        'stock.location.route', 'stock_route_product', 'product_id', 'route_id', 'Routes',
        domain=[('product_selectable', '=', True)],
        help="Depending on the modules installed, this will allow you to define the route of the product: whether it will be bought, manufactured, replenished on order, etc.")
    nbr_reordering_rules = fields.Integer('Reordering Rules', compute='_compute_nbr_reordering_rules')
    # TDE FIXME: really used ?
    reordering_min_qty = fields.Float(compute='_compute_nbr_reordering_rules')
    reordering_max_qty = fields.Float(compute='_compute_nbr_reordering_rules')
    # TDE FIXME: seems only visible in a view - remove me ?
    route_from_categ_ids = fields.Many2many(
        relation="stock.location.route", string="Category Routes",
        related='categ_id.total_route_ids', readonly=False)

    def _is_cost_method_standard(self):
        return True

    def _compute_quantities(self):
        res = self._compute_quantities_dict()
        for template in self:
            template.qty_available = res[template.id]['qty_available']
            template.virtual_available = res[template.id]['virtual_available']
            template.incoming_qty = res[template.id]['incoming_qty']
            template.outgoing_qty = res[template.id]['outgoing_qty']

    def _product_available(self, name, arg):
        return self._compute_quantities_dict()

    def _compute_quantities_dict(self):
        # TDE FIXME: why not using directly the function fields ?
        variants_available = self.mapped('product_variant_ids')._product_available()
        prod_available = {}
        for template in self:
            qty_available = 0
            virtual_available = 0
            incoming_qty = 0
            outgoing_qty = 0
            for p in template.product_variant_ids:
                qty_available += variants_available[p.id]["qty_available"]
                virtual_available += variants_available[p.id]["virtual_available"]
                incoming_qty += variants_available[p.id]["incoming_qty"]
                outgoing_qty += variants_available[p.id]["outgoing_qty"]
            prod_available[template.id] = {
                "qty_available": qty_available,
                "virtual_available": virtual_available,
                "incoming_qty": incoming_qty,
                "outgoing_qty": outgoing_qty,
            }
        return prod_available

    def _search_qty_available(self, operator, value):
        domain = [('qty_available', operator, value)]
        product_variant_ids = self.env['product.product'].search(domain)
        return [('product_variant_ids', 'in', product_variant_ids.ids)]

    def _search_virtual_available(self, operator, value):
        domain = [('virtual_available', operator, value)]
        product_variant_ids = self.env['product.product'].search(domain)
        return [('product_variant_ids', 'in', product_variant_ids.ids)]

    def _search_incoming_qty(self, operator, value):
        domain = [('incoming_qty', operator, value)]
        product_variant_ids = self.env['product.product'].search(domain)
        return [('product_variant_ids', 'in', product_variant_ids.ids)]

    def _search_outgoing_qty(self, operator, value):
        domain = [('outgoing_qty', operator, value)]
        product_variant_ids = self.env['product.product'].search(domain)
        return [('product_variant_ids', 'in', product_variant_ids.ids)]

    def _compute_nbr_reordering_rules(self):
        res = {k: {'nbr_reordering_rules': 0, 'reordering_min_qty': 0, 'reordering_max_qty': 0} for k in self.ids}
        product_data = self.env['stock.warehouse.orderpoint'].read_group([('product_id.product_tmpl_id', 'in', self.ids)], ['product_id', 'product_min_qty', 'product_max_qty'], ['product_id'])
        for data in product_data:
            product = self.env['product.product'].browse([data['product_id'][0]])
            product_tmpl_id = product.product_tmpl_id.id
            res[product_tmpl_id]['nbr_reordering_rules'] += int(data['product_id_count'])
            res[product_tmpl_id]['reordering_min_qty'] = data['product_min_qty']
            res[product_tmpl_id]['reordering_max_qty'] = data['product_max_qty']
        for template in self:
            template.nbr_reordering_rules = res[template.id]['nbr_reordering_rules']
            template.reordering_min_qty = res[template.id]['reordering_min_qty']
            template.reordering_max_qty = res[template.id]['reordering_max_qty']

    @api.onchange('tracking')
    def onchange_tracking(self):
        return self.mapped('product_variant_ids').onchange_tracking()

    def write(self, vals):
        if 'uom_id' in vals:
            new_uom = self.env['uom.uom'].browse(vals['uom_id'])
            updated = self.filtered(lambda template: template.uom_id != new_uom)
            done_moves = self.env['stock.move'].search([('product_id', 'in', updated.with_context(active_test=False).mapped('product_variant_ids').ids)], limit=1)
            if done_moves:
                raise UserError(_("You cannot change the unit of measure as there are already stock moves for this product. If you want to change the unit of measure, you should rather archive this product and create a new one."))
        if 'type' in vals and vals['type'] != 'product' and sum(self.mapped('nbr_reordering_rules')) != 0:
            raise UserError(_('You still have some active reordering rules on this product. Please archive or delete them first.'))
        if any('type' in vals and vals['type'] != prod_tmpl.type for prod_tmpl in self):
            existing_move_lines = self.env['stock.move.line'].search([
                ('product_id', 'in', self.mapped('product_variant_ids').ids),
                ('state', 'in', ['partially_available', 'assigned']),
            ])
            if existing_move_lines:
                raise UserError(_("You can not change the type of a product that is currently reserved on a stock move. If you need to change the type, you should first unreserve the stock move."))
        return super(ProductTemplate, self).write(vals)

    def action_update_quantity_on_hand(self):
        default_product_id = self.env.context.get('default_product_id', self.product_variant_id.id)
        if self.env.user.user_has_groups('stock.group_stock_multi_locations') or (self.env.user.user_has_groups('stock.group_production_lot') and self.tracking != 'none'):
            product_ref_name = self.name + ' - ' + datetime.today().strftime('%m/%d/%y')
            ctx = {'default_filter': 'product', 'default_product_id': default_product_id, 'default_name': product_ref_name}
            return {
                'type': 'ir.actions.act_window',
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'stock.inventory',
                'context': ctx,
            }
        else:
            wiz = self.env['stock.change.product.qty'].create({'product_id': default_product_id})
            return {
                    'name': _('Update quantity on hand'),
                    'type': 'ir.actions.act_window',
                    'view_mode': 'form',
                    'res_model': 'stock.change.product.qty',
                    'target': 'new',
                    'res_id': wiz.id,
                    'context': {'default_product_id': self.env.context.get('default_product_id')}
                }

    def action_open_quants(self):
        products = self.mapped('product_variant_ids')
        action = self.env.ref('stock.product_open_quants').read()[0]
        action['domain'] = [('product_id', 'in', products.ids)]
        action['context'] = {'search_default_internal_loc': 1}
        return action

    def action_view_orderpoints(self):
        products = self.mapped('product_variant_ids')
        action = self.env.ref('stock.product_open_orderpoint').read()[0]
        if products and len(products) == 1:
            action['context'] = {'default_product_id': products.ids[0], 'search_default_product_id': products.ids[0]}
        else:
            action['domain'] = [('product_id', 'in', products.ids)]
            action['context'] = {}
        return action

    def action_view_stock_move_lines(self):
        self.ensure_one()
        action = self.env.ref('stock.stock_move_line_action').read()[0]
        action['domain'] = [('product_id.product_tmpl_id', 'in', self.ids)]
        return action

    def action_open_product_lot(self):
        self.ensure_one()
        action = self.env.ref('stock.action_production_lot_form').read()[0]
        action['domain'] = [('product_id.product_tmpl_id', '=', self.id)]
        if self.product_variant_count == 1:
            action['context'] = {
                'default_product_id': self.product_variant_id.id,
            }

        return action
예제 #18
0
class HrPaySlip(models.Model):
    _inherit = 'hr.payslip'

    other_allow_ids = fields.One2many('employee.other.allowances',
                                      'payslip_id', 'Other Allowances')

    @api.depends('date_from', 'date_to')
    def _get_total_days(self):
        for record in self:
            if record.date_from and record.date_to:
                from_date = datetime.strptime(record.date_from, '%Y-%m-%d')
                to_date = datetime.strptime(record.date_to, '%Y-%m-%d')
                days = (to_date - from_date).days + 1
                record.total_days = days

    total_days = fields.Float(string="Total Days", compute='_get_total_days')
    days_payable = fields.Float(string="Days Payable")
    leave_days = fields.Float(string="Total Days Leave",
                              compute='compute_leave_days')
    sick_leave = fields.Float(string="Sick Leave")
    paid_leaves = fields.Float(string='Paid Leaves', compute='get_paid_leaves')
    unpaid_leaves = fields.Float(string='Unpaid Leaves',
                                 compute='get_paid_leaves')

    sal_advance = fields.Float('Salary Advance',
                               digits=dp.get_precision('Account'),
                               compute='get_advance_salary_amount')
    overtime_normal = fields.Float('Normal Overtime(Hours)')
    holiday_overtime = fields.Float('Holiday Overtime(Hours)')
    holiday_worked = fields.Float('Holiday Worked')
    advance_balance = fields.Float(digits=dp.get_precision('Account'),
                                   string="Balance Advance",
                                   compute='get_remaining_advance_amount')
    advance_ded = fields.Float('Advance Deduction',
                               digits=dp.get_precision('Account'))
    tel_deduction = fields.Float('Telephone Deduction',
                                 digits=dp.get_precision('Account'))
    other_deductions = fields.One2many('other.deductions', 'payslip_id')
    leave_sal_ids = fields.Many2many(
        'employee.salary.structure',
        domain="[('contract_id', '=', contract_id)]",
        string='Leave Salary Details')
    leave_base_amount = fields.Float('Leave Salary Base Amount')

    @api.depends('employee_id')
    def get_advance_salary_amount(self):
        for rec in self:
            rec.sal_advance = rec.employee_id.get_advance_salary_amount()

    @api.depends('employee_id')
    def get_remaining_advance_amount(self):
        for rec in self:
            rec.advance_balance = rec.employee_id.get_remaining_advance_amount(
            )

    @api.onchange('employee_id')
    def employee_id_change(self):
        if self.employee_id:
            self.advance_ded = self.employee_id.get_advance_salary_deduction_amount(
            )
            self.leave_base_amount = self.contract_id.wage

    @api.onchange('leave_sal_ids')
    def onchange_leave_sal_ids(self):
        if self.leave_sal_ids:
            base_amount = self.contract_id.wage
            for item in self.leave_sal_ids:
                base_amount += item.amount
            self.leave_base_amount = base_amount

    @api.depends('employee_id', 'date_from', 'date_to')
    def get_paid_leaves(self):
        for rec in self:
            if rec.employee_id and rec.date_from and rec.date_to:
                cr = self.env.cr
                query = "SELECT id " \
                        "FROM hr_holidays " \
                        "WHERE (date_from, date_to) OVERLAPS ('%s', '%s')" % (rec.date_from, rec.date_to)
                cr.execute(query)
                qres = cr.dictfetchall()
                leave_ids = []
                for item in qres:
                    leave_ids.append(item['id'])
                paid_leaves = 0.0
                unpaid_leaves = 0.0
                leave_obj = self.env['hr.holidays'].search([
                    ('type', '=', 'remove'), ('id', 'in', leave_ids),
                    ('employee_id', '=', rec.employee_id.id),
                    ('state', '=', 'validate')
                ])
                for leave in leave_obj:
                    start = datetime.strptime(leave.date_from,
                                              '%Y-%m-%d %H:%M:%S')
                    end = datetime.strptime(leave.date_to, '%Y-%m-%d %H:%M:%S')
                    date_generated = [
                        start + timedelta(days=x)
                        for x in range(0, (end - start).days + 1)
                    ]
                    for date in date_generated:
                        if datetime.strptime(
                                rec.date_from,
                                "%Y-%m-%d") <= date <= datetime.strptime(
                                    rec.date_to, "%Y-%m-%d"):
                            if leave.holiday_status_id.paid_leave:
                                paid_leaves += 1
                            else:
                                unpaid_leaves += 1
                rec.paid_leaves = paid_leaves
                rec.unpaid_leaves = unpaid_leaves

    @api.depends('paid_leaves', 'unpaid_leaves')
    def compute_leave_days(self):
        for rec in self:
            rec.leave_days = rec.paid_leaves + rec.unpaid_leaves
            rec.days_payable = rec.total_days - rec.leave_days

    @api.model
    def create(self, vals):
        payslip = super(HrPaySlip, self).create(vals)
        if payslip.payslip_run_id:
            deduction_amount = payslip.employee_id.get_advance_salary_deduction_amount(
            )
            payslip.payslip_run_id.write({
                'sal_detail_ids': [(0, 0, {
                    'payslip_id': payslip.id,
                    'advance_ded': deduction_amount
                })]
            })
        return payslip

    @api.multi
    def write(self, vals):
        payslip_batch_id = self.payslip_run_id
        payslip = super(HrPaySlip, self).write(vals)
        if 'payslip_run_id' in vals:
            if vals['payslip_run_id']:
                deduction_amount = self.employee_id.get_advance_salary_deduction_amount(
                )
                self.payslip_run_id.write({
                    'sal_detail_ids': [(0, 0, {
                        'payslip_id': self.id,
                        'advance_ded': deduction_amount
                    })]
                })
            else:
                sal_details_obj = payslip_batch_id.sal_detail_ids.search([
                    ('payslip_id', '=', self.id)
                ])
                sal_details_obj.unlink()
        return payslip

    @api.multi
    def unlink(self):
        for rec in self:
            if rec.payslip_run_id:
                obj = self.env['payroll.emp.line'].search([
                    ('payslip_id', '=', rec.id),
                    ('payroll_id', '=', rec.payslip_run_id.id)
                ])
                if obj:
                    for item in obj:
                        item.unlink()
        return super(HrPaySlip, self).unlink()

    @api.multi
    def get_encashable_leave_days(self):
        return self.employee_id.get_encashable_leave_days(
            self.date_from, self.date_to)

    @api.multi
    def action_payslip_done(self):
        for line in self.line_ids:
            if line.code == 'ADVDED':
                advance_obj = self.env['employee.advance.salary'].search([
                    ('employee_id', '=', self.employee_id.id),
                    ('state', '=', 'paid')
                ])
                vals = {
                    'payslip_id': self.id,
                    'deduction_amount': abs(line.amount),
                    'advance_id': advance_obj.id,
                }
                advance_line_obj = self.env['advance.salary.deduction'].create(
                    vals)
                advance_line_obj.advance_done()
        res = super(HrPaySlip, self).action_payslip_done()
        if res:
            advance_sal_obj = self.env['employee.advance.salary'].search(
                [('employee_id', '=', self.employee_id.id),
                 ('state', '=', 'confirm'), ('pay_in_next_salary', '=', True)],
                limit=1)
            if advance_sal_obj:
                advance_sal_obj.write({'state': 'paid'})
            encashment_obj = self.env['employee.leave.encashment'].search(
                [('employee_id', '=', self.employee_id.id),
                 ('state', '=', 'confirm'), ('pay_in_next_salary', '=', True)],
                limit=1)
            if encashment_obj:
                for encashment in encashment_obj:
                    encashment.action_paid()
        return res
예제 #19
0
class supplier_statements_report_with_goods(models.TransientModel):
    _name = "supplier.statements.report.with.goods"
    _description = u"供应商对账单带商品明细"

    partner_id = fields.Many2one('partner', string=u'业务伙伴', readonly=True)
    name = fields.Char(string=u'单据编号', readonly=True)
    date = fields.Date(string=u'单据日期', readonly=True)
    done_date = fields.Date(string=u'完成日期', readonly=True)
    category_id = fields.Many2one('core.category', u'商品类别')
    goods_code = fields.Char(u'商品编号')
    goods_name = fields.Char(u'商品名称')
    attribute_id = fields.Many2one('attribute', u'规格型号')
    uom_id = fields.Many2one('uom', u'单位')
    quantity = fields.Float(u'数量',
                            digits=dp.get_precision('Quantity'))
    price = fields.Float(u'单价',
                         digits=dp.get_precision('Amount'))
    discount_amount = fields.Float(u'折扣额',
                                   digits=dp.get_precision('Amount'))
    without_tax_amount = fields.Float(u'不含税金额',
                                      digits=dp.get_precision('Amount'))
    tax_amount = fields.Float(u'税额',
                              digits=dp.get_precision('Amount'))
    order_amount = fields.Float(string=u'采购金额', readonly=True,
                                digits=dp.get_precision('Amount'))  # 采购
    benefit_amount = fields.Float(string=u'优惠金额', readonly=True,
                                  digits=dp.get_precision('Amount'))
    fee = fields.Float(string=u'客户承担费用', readonly=True,
                       digits=dp.get_precision('Amount'))
    amount = fields.Float(string=u'应付金额', readonly=True,
                          digits=dp.get_precision('Amount'))
    pay_amount = fields.Float(string=u'实际付款金额', readonly=True,
                              digits=dp.get_precision('Amount'))
    discount_money = fields.Float(string=u'付款折扣', readonly=True,
                                  digits=dp.get_precision('Amount'))
    balance_amount = fields.Float(string=u'应付款余额', readonly=True,
                                  digits=dp.get_precision('Amount'))
    note = fields.Char(string=u'备注', readonly=True)
    move_id = fields.Many2one('wh.move', string=u'出入库单', readonly=True)

    @api.multi
    def find_source_order(self):
        # 三情况:收付款单、采购退货单、采购入库单、核销单
        self.ensure_one()
        model_view = {
            'money.order': {'name': u'付款单',
                            'view': 'money.money_order_form'},
            'buy.receipt': {'name': u'采购入库单',
                            'view': 'buy.buy_receipt_form',
                            'name_return': u'采购退货单',
                            'view_return': 'buy.buy_return_form'},
            'reconcile.order': {'name': u'核销单',
                                'view': 'money.reconcile_order_form'}
        }
        for model, view_dict in model_view.iteritems():
            res = self.env[model].search([('name', '=', self.name)])
            name = model == 'buy.receipt' and res.is_return and \
                   view_dict['name_return'] or view_dict['name']
            view = model == 'buy.receipt' and res.is_return and \
                   self.env.ref(view_dict['view_return']) \
                   or self.env.ref(view_dict['view'])
            if res:
                return {
                    'name': name,
                    'view_mode': 'form',
                    'view_id': False,
                    'views': [(view.id, 'form')],
                    'res_model': model,
                    'type': 'ir.actions.act_window',
                    'res_id': res.id,
                }
        raise UserError(u'期初余额没有原始单据可供查看。')
예제 #20
0
class MrpWorkorder(models.Model):
    _name = 'mrp.workorder'
    _description = 'Work Order'
    _inherit = ['mail.thread', 'mail.activity.mixin', 'mrp.abstract.workorder']

    name = fields.Char(
        'Work Order', required=True,
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
    workcenter_id = fields.Many2one(
        'mrp.workcenter', 'Work Center', required=True,
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
    working_state = fields.Selection(
        'Workcenter Status', related='workcenter_id.working_state', readonly=False,
        help='Technical: used in views only')
    production_availability = fields.Selection(
        'Stock Availability', readonly=True,
        related='production_id.reservation_state', store=True,
        help='Technical: used in views and domains only.')
    production_state = fields.Selection(
        'Production State', readonly=True,
        related='production_id.state',
        help='Technical: used in views only.')
    qty_production = fields.Float('Original Production Quantity', readonly=True, related='production_id.product_qty')
    qty_remaining = fields.Float('Quantity To Be Produced', compute='_compute_qty_remaining', digits=dp.get_precision('Product Unit of Measure'))
    qty_produced = fields.Float(
        'Quantity', default=0.0,
        readonly=True,
        digits=dp.get_precision('Product Unit of Measure'),
        help="The number of products already handled by this work order")
    is_produced = fields.Boolean(string="Has Been Produced",
        compute='_compute_is_produced')
    state = fields.Selection([
        ('pending', 'Waiting for another WO'),
        ('ready', 'Ready'),
        ('progress', 'In Progress'),
        ('done', 'Finished'),
        ('cancel', 'Cancelled')], string='Status',
        default='pending')
    leave_id = fields.Many2one(
        'resource.calendar.leaves',
        help='Slot into workcenter calendar once planned')
    date_planned_start = fields.Datetime(
        'Scheduled Date Start',
        compute='_compute_dates_planned',
        inverse='_set_dates_planned',
        search='_search_date_planned_start',
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
    date_planned_finished = fields.Datetime(
        'Scheduled Date Finished',
        compute='_compute_dates_planned',
        inverse='_set_dates_planned',
        search='_search_date_planned_finished',
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
    date_start = fields.Datetime(
        'Effective Start Date',
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
    date_finished = fields.Datetime(
        'Effective End Date',
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})

    duration_expected = fields.Float(
        'Expected Duration', digits=(16, 2),
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
        help="Expected duration (in minutes)")
    duration = fields.Float(
        'Real Duration', compute='_compute_duration',
        readonly=True, store=True)
    duration_unit = fields.Float(
        'Duration Per Unit', compute='_compute_duration',
        readonly=True, store=True)
    duration_percent = fields.Integer(
        'Duration Deviation (%)', compute='_compute_duration',
        group_operator="avg", readonly=True, store=True)

    operation_id = fields.Many2one(
        'mrp.routing.workcenter', 'Operation')  # Should be used differently as BoM can change in the meantime
    worksheet = fields.Binary(
        'Worksheet', related='operation_id.worksheet', readonly=True)
    move_raw_ids = fields.One2many(
        'stock.move', 'workorder_id', 'Raw Moves',
        domain=[('raw_material_production_id', '!=', False), ('production_id', '=', False)])
    move_finished_ids = fields.One2many(
        'stock.move', 'workorder_id', 'Finished Moves',
        domain=[('raw_material_production_id', '=', False), ('production_id', '!=', False)])
    move_line_ids = fields.One2many(
        'stock.move.line', 'workorder_id', 'Moves to Track',
        help="Inventory moves for which you must scan a lot number at this work order")
    finished_lot_id = fields.Many2one(
        'stock.production.lot', 'Lot/Serial Number', domain="[('product_id', '=', product_id)]",
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
    time_ids = fields.One2many(
        'mrp.workcenter.productivity', 'workorder_id')
    is_user_working = fields.Boolean(
        'Is the Current User Working', compute='_compute_working_users',
        help="Technical field indicating whether the current user is working. ")
    working_user_ids = fields.One2many('res.users', string='Working user on this work order.', compute='_compute_working_users')
    last_working_user_id = fields.One2many('res.users', string='Last user that worked on this work order.', compute='_compute_working_users')

    next_work_order_id = fields.Many2one('mrp.workorder', "Next Work Order")
    scrap_ids = fields.One2many('stock.scrap', 'workorder_id')
    scrap_count = fields.Integer(compute='_compute_scrap_move_count', string='Scrap Move')
    production_date = fields.Datetime('Production Date', related='production_id.date_planned_start', store=True, readonly=False)
    color = fields.Integer('Color', compute='_compute_color')
    capacity = fields.Float(
        'Capacity', default=1.0,
        help="Number of pieces that can be produced in parallel.")
    raw_workorder_line_ids = fields.One2many('mrp.workorder.line',
        'raw_workorder_id', string='Components')
    finished_workorder_line_ids = fields.One2many('mrp.workorder.line',
        'finished_workorder_id', string='By-products')
    allowed_lots_domain = fields.One2many(comodel_name='stock.production.lot', compute="_compute_allowed_lots_domain")

    # Both `date_planned_start` and `date_planned_finished` are related fields on `leave_id`. Let's say
    # we slide a workorder on a gantt view, a single call to write is made with both
    # fields Changes. As the ORM doesn't batch the write on related fields and instead
    # makes multiple call, the constraint check_dates() is raised.
    # That's why the compute and set methods are needed. to ensure the dates are updated
    # in the same time. The two next search method are needed as the field are non stored and
    # not direct related fields.
    @api.depends('leave_id')
    def _compute_dates_planned(self):
        for workorder in self:
            workorder.date_planned_start = workorder.leave_id.date_from
            workorder.date_planned_finished = workorder.leave_id.date_to

    def _set_dates_planned(self):
        date_from = self.date_planned_start[0]
        date_to = self.date_planned_finished[0]
        self.mapped('leave_id').write({
            'date_from': date_from,
            'date_to': date_to,
        })

    def _search_date_planned_start(self, operator, value):
        return [('leave_id.date_from', operator, value)]

    def _search_date_planned_finished(self, operator, value):
        return [('leave_id.date_to', operator, value)]

    @api.onchange('finished_lot_id')
    def _onchange_finished_lot_id(self):
        """When the user changes the lot being currently produced, suggest
        a quantity to produce consistent with the previous workorders. """
        previous_wo = self.env['mrp.workorder'].search([
            ('next_work_order_id', '=', self.id)
        ])
        if previous_wo:
            line = previous_wo.finished_workorder_line_ids.filtered(lambda line: line.product_id == self.product_id and line.lot_id == self.finished_lot_id)
            if line:
                self.qty_producing = line.qty_done

    @api.depends('production_id.workorder_ids.finished_workorder_line_ids',
    'production_id.workorder_ids.finished_workorder_line_ids.qty_done',
    'production_id.workorder_ids.finished_workorder_line_ids.lot_id')
    def _compute_allowed_lots_domain(self):
        """ Check if all the finished products has been assigned to a serial
        number or a lot in other workorders. If yes, restrict the selectable lot
        to the lot/sn used in other workorders.
        """
        productions = self.mapped('production_id')
        for production in productions:
            if production.product_id.tracking == 'none':
                continue

            rounding = production.product_uom_id.rounding
            finished_workorder_lines = production.workorder_ids.mapped('finished_workorder_line_ids').filtered(lambda wl: wl.product_id == production.product_id)
            qties_done_per_lot = defaultdict(list)
            for finished_workorder_line in finished_workorder_lines:
                # It is possible to have finished workorder lines without a lot (eg using the dummy
                # test type). Ignore them when computing the allowed lots.
                if finished_workorder_line.lot_id:
                    qties_done_per_lot[finished_workorder_line.lot_id.id].append(finished_workorder_line.qty_done)

            qty_to_produce = production.product_qty
            allowed_lot_ids = self.env['stock.production.lot']
            qty_produced = sum([max(qty_dones) for qty_dones in qties_done_per_lot.values()])
            if float_compare(qty_produced, qty_to_produce, precision_rounding=rounding) < 0:
                # If we haven't produced enough, all lots are available
                allowed_lot_ids = self.env['stock.production.lot'].search([('product_id', '=', production.product_id.id)])
            else:
                # If we produced enough, only the already produced lots are available
                allowed_lot_ids = self.env['stock.production.lot'].browse(qties_done_per_lot.keys())
            workorders = production.workorder_ids.filtered(lambda wo: wo.state not in ('done', 'cancel'))
            for workorder in workorders:
                if workorder.product_tracking == 'serial':
                    workorder.allowed_lots_domain = allowed_lot_ids - workorder.finished_workorder_line_ids.filtered(lambda wl: wl.product_id == production.product_id).mapped('lot_id')
                else:
                    workorder.allowed_lots_domain = allowed_lot_ids

    @api.multi
    def name_get(self):
        return [(wo.id, "%s - %s - %s" % (wo.production_id.name, wo.product_id.name, wo.name)) for wo in self]

    @api.one
    @api.depends('production_id.product_qty', 'qty_produced')
    def _compute_is_produced(self):
        rounding = self.production_id.product_uom_id.rounding
        self.is_produced = float_compare(self.qty_produced, self.production_id.product_qty, precision_rounding=rounding) >= 0

    @api.one
    @api.depends('time_ids.duration', 'qty_produced')
    def _compute_duration(self):
        self.duration = sum(self.time_ids.mapped('duration'))
        self.duration_unit = round(self.duration / max(self.qty_produced, 1), 2)  # rounding 2 because it is a time
        if self.duration_expected:
            self.duration_percent = 100 * (self.duration_expected - self.duration) / self.duration_expected
        else:
            self.duration_percent = 0

    def _compute_working_users(self):
        """ Checks whether the current user is working, all the users currently working and the last user that worked. """
        for order in self:
            order.working_user_ids = [(4, order.id) for order in order.time_ids.filtered(lambda time: not time.date_end).sorted('date_start').mapped('user_id')]
            if order.working_user_ids:
                order.last_working_user_id = order.working_user_ids[-1]
            elif order.time_ids:
                order.last_working_user_id = order.time_ids.sorted('date_end')[-1].user_id
            if order.time_ids.filtered(lambda x: (x.user_id.id == self.env.user.id) and (not x.date_end) and (x.loss_type in ('productive', 'performance'))):
                order.is_user_working = True
            else:
                order.is_user_working = False

    @api.multi
    def _compute_scrap_move_count(self):
        data = self.env['stock.scrap'].read_group([('workorder_id', 'in', self.ids)], ['workorder_id'], ['workorder_id'])
        count_data = dict((item['workorder_id'][0], item['workorder_id_count']) for item in data)
        for workorder in self:
            workorder.scrap_count = count_data.get(workorder.id, 0)

    @api.multi
    @api.depends('date_planned_finished', 'production_id.date_planned_finished')
    def _compute_color(self):
        late_orders = self.filtered(lambda x: x.production_id.date_planned_finished and x.date_planned_finished > x.production_id.date_planned_finished)
        for order in late_orders:
            order.color = 4
        for order in (self - late_orders):
            order.color = 2

    @api.multi
    def write(self, values):
        if list(values.keys()) != ['time_ids'] and any(workorder.state == 'done' for workorder in self):
            raise UserError(_('You can not change the finished work order.'))
        if 'date_planned_start' in values or 'date_planned_finished' in values:
            for workorder in self:
                start_date = fields.Datetime.to_datetime(values.get('date_planned_start')) or workorder.date_planned_start
                end_date = fields.Datetime.to_datetime(values.get('date_planned_finished')) or workorder.date_planned_finished
                if start_date and end_date and start_date > end_date:
                    raise UserError(_('The planned end date of the work order cannot be prior to the planned start date, please correct this to save the work order.'))
        return super(MrpWorkorder, self).write(values)

    def _generate_wo_lines(self):
        """ Generate workorder line """
        self.ensure_one()
        moves = (self.move_raw_ids | self.move_finished_ids).filtered(
            lambda move: move.state not in ('done', 'cancel')
        )
        for move in moves:
            qty_to_consume = self._prepare_component_quantity(move, self.qty_producing)
            line_values = self._generate_lines_values(move, qty_to_consume)
            self.env['mrp.workorder.line'].create(line_values)

    def _apply_update_workorder_lines(self):
        """ update existing line on the workorder. It could be trigger manually
        after a modification of qty_producing.
        """
        self.ensure_one()
        line_values = self._update_workorder_lines()
        self.env['mrp.workorder.line'].create(line_values['to_create'])
        if line_values['to_delete']:
            line_values['to_delete'].unlink()
        for line, vals in line_values['to_update'].items():
            line.write(vals)

    def _refresh_wo_lines(self):
        """ Modify exisiting workorder line in order to match the reservation on
        stock move line. The strategy is to remove the line that were not
        processed yet then call _generate_lines_values that recreate workorder
        line depending the reservation.
        """
        for workorder in self:
            raw_moves = workorder.move_raw_ids.filtered(
                lambda move: move.state not in ('done', 'cancel')
            )
            wl_to_unlink = self.env['mrp.workorder.line']
            for move in raw_moves:
                rounding = move.product_uom.rounding
                qty_already_consumed = 0.0
                workorder_lines = workorder.raw_workorder_line_ids.filtered(lambda w: w.move_id == move)
                for wl in workorder_lines:
                    if not wl.qty_done:
                        wl_to_unlink |= wl
                        continue

                    qty_already_consumed += wl.qty_done
                qty_to_consume = self._prepare_component_quantity(move, workorder.qty_producing)
                wl_to_unlink.unlink()
                if float_compare(qty_to_consume, qty_already_consumed, precision_rounding=rounding) > 0:
                    line_values = workorder._generate_lines_values(move, qty_to_consume - qty_already_consumed)
                    self.env['mrp.workorder.line'].create(line_values)

    def _defaults_from_finished_workorder_line(self, reference_lot_lines):
        for r_line in reference_lot_lines:
            # see which lot we could suggest and its related qty_producing
            if not r_line.lot_id:
                continue
            candidates = self.finished_workorder_line_ids.filtered(lambda line: line.lot_id == r_line.lot_id)
            rounding = self.product_uom_id.rounding
            if not candidates:
                self.write({
                    'finished_lot_id': r_line.lot_id.id,
                    'qty_producing': r_line.qty_done,
                })
                return True
            elif float_compare(candidates.qty_done, r_line.qty_done, precision_rounding=rounding) < 0:
                self.write({
                    'finished_lot_id': r_line.lot_id.id,
                    'qty_producing': r_line.qty_done - candidates.qty_done,
                })
                return True
        return False

    @api.multi
    def record_production(self):
        if not self:
            return True

        self.ensure_one()
        if float_compare(self.qty_producing, 0, precision_rounding=self.product_uom_id.rounding) <= 0:
            raise UserError(_('Please set the quantity you are currently producing. It should be different from zero.'))

        # If last work order, then post lots used
        if not self.next_work_order_id:
            self._update_finished_move()

        # Transfer quantities from temporary to final move line or make them final
        self._update_moves()

        # Transfer lot (if present) and quantity produced to a finished workorder line
        if self.product_tracking != 'none':
            self._create_or_update_finished_line()

        # Update workorder quantity produced
        self.qty_produced += self.qty_producing

        # Suggest a finished lot on the next workorder
        if self.next_work_order_id and self.production_id.product_id.tracking != 'none' and not self.next_work_order_id.finished_lot_id:
            self.next_work_order_id._defaults_from_finished_workorder_line(self.finished_workorder_line_ids)
            # As we may have changed the quantity to produce on the next workorder,
            # make sure to update its wokorder lines
            self.next_work_order_id._apply_update_workorder_lines()

        # One a piece is produced, you can launch the next work order
        self._start_nextworkorder()

        # Test if the production is done
        rounding = self.production_id.product_uom_id.rounding
        if float_compare(self.qty_produced, self.production_id.product_qty, precision_rounding=rounding) < 0:
            previous_wo = self.env['mrp.workorder']
            if self.product_tracking != 'none':
                previous_wo = self.env['mrp.workorder'].search([
                    ('next_work_order_id', '=', self.id)
                ])
            candidate_found_in_previous_wo = False
            if previous_wo:
                candidate_found_in_previous_wo = self._defaults_from_finished_workorder_line(previous_wo.finished_workorder_line_ids)
            if not candidate_found_in_previous_wo:
                # self is the first workorder
                self.qty_producing = self.qty_remaining
                self.finished_lot_id = False
                if self.product_tracking == 'serial':
                    self.qty_producing = 1

            self._apply_update_workorder_lines()
        else:
            self.qty_producing = 0
            self.button_finish()
        return True

    def _get_byproduct_move_to_update(self):
        return self.production_id.move_finished_ids.filtered(lambda x: (x.product_id.id != self.production_id.product_id.id) and (x.state not in ('done', 'cancel')))

    def _create_or_update_finished_line(self):
        """
        1. Check that the final lot and the quantity producing is valid regarding
            other workorders of this production
        2. Save final lot and quantity producing to suggest on next workorder
        """
        self.ensure_one()
        final_lot_quantity = self.qty_production
        rounding = self.product_uom_id.rounding
        # Get the max quantity possible for current lot in other workorders
        for workorder in (self.production_id.workorder_ids - self):
            # We add the remaining quantity to the produced quantity for the
            # current lot. For 5 finished products: if in the first wo it
            # creates 4 lot A and 1 lot B and in the second it create 3 lot A
            # and it remains 2 units to product, it could produce 5 lot A.
            # In this case we select 4 since it would conflict with the first
            # workorder otherwise.
            line = workorder.finished_workorder_line_ids.filtered(lambda line: line.lot_id == self.finished_lot_id)
            line_without_lot = workorder.finished_workorder_line_ids.filtered(lambda line: line.product_id == workorder.product_id and not line.lot_id)
            quantity_remaining = workorder.qty_remaining + line_without_lot.qty_done
            quantity = line.qty_done + quantity_remaining
            if line and float_compare(quantity, final_lot_quantity, precision_rounding=rounding) <= 0:
                final_lot_quantity = quantity
            elif float_compare(quantity_remaining, final_lot_quantity, precision_rounding=rounding) < 0:
                final_lot_quantity = quantity_remaining

        # final lot line for this lot on this workorder.
        current_lot_lines = self.finished_workorder_line_ids.filtered(lambda line: line.lot_id == self.finished_lot_id)

        # this lot has already been produced
        if float_compare(final_lot_quantity, current_lot_lines.qty_done + self.qty_producing, precision_rounding=rounding) < 0:
            raise UserError(_('You have produced %s %s of lot %s in the previous workorder. You are trying to produce %s in this one') %
                (final_lot_quantity, self.product_id.uom_id.name, self.finished_lot_id.name, current_lot_lines.qty_done + self.qty_producing))

        # Update workorder line that regiter final lot created
        if not current_lot_lines:
            current_lot_lines = self.env['mrp.workorder.line'].create({
                'finished_workorder_id': self.id,
                'product_id': self.product_id.id,
                'lot_id': self.finished_lot_id.id,
                'qty_done': self.qty_producing,
            })
        else:
            current_lot_lines.qty_done += self.qty_producing

    @api.multi
    def _start_nextworkorder(self):
        rounding = self.product_id.uom_id.rounding
        if self.next_work_order_id.state == 'pending' and (
                (self.operation_id.batch == 'no' and
                 float_compare(self.qty_production, self.qty_produced, precision_rounding=rounding) <= 0) or
                (self.operation_id.batch == 'yes' and
                 float_compare(self.operation_id.batch_size, self.qty_produced, precision_rounding=rounding) <= 0)):
            self.next_work_order_id.state = 'ready'

    @api.multi
    def button_start(self):
        self.ensure_one()
        # As button_start is automatically called in the new view
        if self.state in ('done', 'cancel'):
            return True

        # Need a loss in case of the real time exceeding the expected
        timeline = self.env['mrp.workcenter.productivity']
        if self.duration < self.duration_expected:
            loss_id = self.env['mrp.workcenter.productivity.loss'].search([('loss_type','=','productive')], limit=1)
            if not len(loss_id):
                raise UserError(_("You need to define at least one productivity loss in the category 'Productivity'. Create one from the Manufacturing app, menu: Configuration / Productivity Losses."))
        else:
            loss_id = self.env['mrp.workcenter.productivity.loss'].search([('loss_type','=','performance')], limit=1)
            if not len(loss_id):
                raise UserError(_("You need to define at least one productivity loss in the category 'Performance'. Create one from the Manufacturing app, menu: Configuration / Productivity Losses."))
        if self.production_id.state != 'progress':
            self.production_id.write({
                'date_start': datetime.now(),
            })
        timeline.create({
            'workorder_id': self.id,
            'workcenter_id': self.workcenter_id.id,
            'description': _('Time Tracking: ')+self.env.user.name,
            'loss_id': loss_id[0].id,
            'date_start': datetime.now(),
            'user_id': self.env.user.id
        })
        return self.write({'state': 'progress',
                    'date_start': datetime.now(),
        })

    @api.multi
    def button_finish(self):
        self.ensure_one()
        self.end_all()
        return self.write({'state': 'done', 'date_finished': fields.Datetime.now()})

    @api.multi
    def end_previous(self, doall=False):
        """
        @param: doall:  This will close all open time lines on the open work orders when doall = True, otherwise
        only the one of the current user
        """
        # TDE CLEANME
        timeline_obj = self.env['mrp.workcenter.productivity']
        domain = [('workorder_id', 'in', self.ids), ('date_end', '=', False)]
        if not doall:
            domain.append(('user_id', '=', self.env.user.id))
        not_productive_timelines = timeline_obj.browse()
        for timeline in timeline_obj.search(domain, limit=None if doall else 1):
            wo = timeline.workorder_id
            if wo.duration_expected <= wo.duration:
                if timeline.loss_type == 'productive':
                    not_productive_timelines += timeline
                timeline.write({'date_end': fields.Datetime.now()})
            else:
                maxdate = fields.Datetime.from_string(timeline.date_start) + relativedelta(minutes=wo.duration_expected - wo.duration)
                enddate = datetime.now()
                if maxdate > enddate:
                    timeline.write({'date_end': enddate})
                else:
                    timeline.write({'date_end': maxdate})
                    not_productive_timelines += timeline.copy({'date_start': maxdate, 'date_end': enddate})
        if not_productive_timelines:
            loss_id = self.env['mrp.workcenter.productivity.loss'].search([('loss_type', '=', 'performance')], limit=1)
            if not len(loss_id):
                raise UserError(_("You need to define at least one unactive productivity loss in the category 'Performance'. Create one from the Manufacturing app, menu: Configuration / Productivity Losses."))
            not_productive_timelines.write({'loss_id': loss_id.id})
        return True

    @api.multi
    def end_all(self):
        return self.end_previous(doall=True)

    @api.multi
    def button_pending(self):
        self.end_previous()
        return True

    @api.multi
    def button_unblock(self):
        for order in self:
            order.workcenter_id.unblock()
        return True

    @api.multi
    def action_cancel(self):
        return self.write({'state': 'cancel'})

    @api.multi
    def button_done(self):
        if any([x.state in ('done', 'cancel') for x in self]):
            raise UserError(_('A Manufacturing Order is already done or cancelled.'))
        self.end_all()
        return self.write({'state': 'done',
                    'date_finished': datetime.now()})

    @api.multi
    def button_scrap(self):
        self.ensure_one()
        return {
            'name': _('Scrap'),
            'view_mode': 'form',
            'res_model': 'stock.scrap',
            'view_id': self.env.ref('stock.stock_scrap_form_view2').id,
            'type': 'ir.actions.act_window',
            'context': {'default_workorder_id': self.id, 'default_production_id': self.production_id.id, 'product_ids': (self.production_id.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel')) | self.production_id.move_finished_ids.filtered(lambda x: x.state == 'done')).mapped('product_id').ids},
            # 'context': {'product_ids': self.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel')).mapped('product_id').ids + [self.production_id.product_id.id]},
            'target': 'new',
        }

    @api.multi
    def action_see_move_scrap(self):
        self.ensure_one()
        action = self.env.ref('stock.action_stock_scrap').read()[0]
        action['domain'] = [('workorder_id', '=', self.id)]
        return action

    @api.multi
    @api.depends('qty_production', 'qty_produced')
    def _compute_qty_remaining(self):
        for wo in self:
            wo.qty_remaining = float_round(wo.qty_production - wo.qty_produced, precision_rounding=wo.production_id.product_uom_id.rounding)
예제 #21
0
class PurchaseRequisitionLine(models.Model):
    _name = "purchase.requisition.line"
    _description = "Purchase Requisition Line"
    _rec_name = 'product_id'

    product_id = fields.Many2one('product.product',
                                 string='Product',
                                 domain=[('purchase_ok', '=', True)],
                                 required=True)
    product_uom_id = fields.Many2one('uom.uom',
                                     string='Product Unit of Measure')
    product_qty = fields.Float(
        string='Quantity', digits=dp.get_precision('Product Unit of Measure'))
    price_unit = fields.Float(string='Unit Price',
                              digits=dp.get_precision('Product Price'))
    qty_ordered = fields.Float(compute='_compute_ordered_qty',
                               string='Ordered Quantities')
    requisition_id = fields.Many2one('purchase.requisition',
                                     required=True,
                                     string='Purchase Agreement',
                                     ondelete='cascade')
    company_id = fields.Many2one(
        'res.company',
        related='requisition_id.company_id',
        string='Company',
        store=True,
        readonly=True,
        default=lambda self: self.env['res.company']._company_default_get(
            'purchase.requisition.line'))
    account_analytic_id = fields.Many2one('account.analytic.account',
                                          string='Analytic Account')
    analytic_tag_ids = fields.Many2many('account.analytic.tag',
                                        string='Analytic Tags')
    schedule_date = fields.Date(string='Scheduled Date')
    move_dest_id = fields.Many2one('stock.move', 'Downstream Move')
    supplier_info_ids = fields.One2many('product.supplierinfo',
                                        'purchase_requisition_line_id')

    @api.model
    def create(self, vals):
        res = super(PurchaseRequisitionLine, self).create(vals)
        if res.requisition_id.state not in [
                'draft', 'cancel', 'done'
        ] and res.requisition_id.is_quantity_copy == 'none':
            supplier_infos = self.env['product.supplierinfo'].search([
                ('product_id', '=', vals.get('product_id')),
                ('name', '=', res.requisition_id.vendor_id.id),
            ])
            if not [s.requisition_id for s in supplier_infos]:
                res.create_supplier_info()
            if vals['price_unit'] <= 0.0:
                raise UserError(
                    _('You cannot confirm the blanket order without price.'))
        return res

    @api.multi
    def write(self, vals):
        res = super(PurchaseRequisitionLine, self).write(vals)
        if 'price_unit' in vals:
            if vals['price_unit'] <= 0.0:
                raise UserError(
                    _('You cannot confirm the blanket order without price.'))
            # If the price is updated, we have to update the related SupplierInfo
            self.supplier_info_ids.write({'price': vals['price_unit']})
        return res

    def unlink(self):
        to_unlink = self.filtered(lambda r: r.requisition_id.state not in
                                  ['draft', 'cancel', 'done'])
        to_unlink.mapped('supplier_info_ids').unlink()
        return super(PurchaseRequisitionLine, self).unlink()

    def create_supplier_info(self):
        purchase_requisition = self.requisition_id
        if purchase_requisition.type_id.quantity_copy == 'none' and purchase_requisition.vendor_id:
            # create a supplier_info only in case of blanket order
            self.env['product.supplierinfo'].create({
                'name':
                purchase_requisition.vendor_id.id,
                'product_id':
                self.product_id.id,
                'product_tmpl_id':
                self.product_id.product_tmpl_id.id,
                'price':
                self.price_unit,
                'currency_id':
                self.requisition_id.currency_id.id,
                'purchase_requisition_id':
                purchase_requisition.id,
                'purchase_requisition_line_id':
                self.id,
            })

    @api.multi
    @api.depends('requisition_id.purchase_ids.state')
    def _compute_ordered_qty(self):
        for line in self:
            total = 0.0
            for po in line.requisition_id.purchase_ids.filtered(
                    lambda purchase_order: purchase_order.state in
                ['purchase', 'done']):
                for po_line in po.order_line.filtered(
                        lambda order_line: order_line.product_id == line.
                        product_id):
                    if po_line.product_uom != line.product_uom_id:
                        total += po_line.product_uom._compute_quantity(
                            po_line.product_qty, line.product_uom_id)
                    else:
                        total += po_line.product_qty
            line.qty_ordered = total

    @api.onchange('product_id')
    def _onchange_product_id(self):
        if self.product_id:
            self.product_uom_id = self.product_id.uom_id
            self.product_qty = 1.0
        if not self.schedule_date:
            self.schedule_date = self.requisition_id.schedule_date

    @api.multi
    def _prepare_purchase_order_line(self,
                                     name,
                                     product_qty=0.0,
                                     price_unit=0.0,
                                     taxes_ids=False):
        self.ensure_one()
        requisition = self.requisition_id
        if requisition.schedule_date:
            date_planned = datetime.combine(requisition.schedule_date,
                                            time.min)
        else:
            date_planned = datetime.now()
        return {
            'name':
            name,
            'product_id':
            self.product_id.id,
            'product_uom':
            self.product_id.uom_po_id.id,
            'product_qty':
            product_qty,
            'price_unit':
            price_unit,
            'taxes_id': [(6, 0, taxes_ids)],
            'date_planned':
            date_planned,
            'account_analytic_id':
            self.account_analytic_id.id,
            'analytic_tag_ids':
            self.analytic_tag_ids.ids,
            'move_dest_ids':
            self.move_dest_id and [(4, self.move_dest_id.id)] or []
        }
예제 #22
0
class BoCardLine(models.Model):
    _name = 'bo.card.line'
    _description = 'Bitodoo card line'

    card_id = fields.Many2one('bo.card', 'Card', ondelete='cascade')
    card_location_id = fields.Many2one(related='card_id.location_id')
    move_line_id = fields.Many2one('stock.move.line', string='Move line')
    date = fields.Datetime(related='move_line_id.date', string='Date move')
    set_date = fields.Datetime(related='move_line_id.set_date')
    # For get before card line (Not get with fields date)
    datetime = fields.Datetime(string='Date card',
                               compute='_compute_date_card',
                               store=True)
    reference = fields.Char(related='move_line_id.reference')
    product_id = fields.Many2one(related='move_line_id.product_id')
    location_id = fields.Many2one(related='move_line_id.location_id')
    location_dest_id = fields.Many2one(related='move_line_id.location_dest_id')
    lot_id = fields.Many2one(related='move_line_id.lot_id')
    package_id = fields.Many2one(related='move_line_id.package_id')
    result_package_id = fields.Many2one(
        related='move_line_id.result_package_id')
    standard_price = fields.Float('Standard price',
                                  compute='_compute_standard_price',
                                  digits=dp.get_precision('Product Price'))

    # In
    in_quantity = fields.Float('In quantity')
    in_cost = fields.Float(
        'In cost', digits=dp.get_precision('Product Price History Card'))
    in_cost_total = fields.Float('In cost total',
                                 digits=dp.get_precision('Product Price'))

    # Out
    out_quantity = fields.Float('Out quantity')
    out_cost = fields.Float(
        'Out cost', digits=dp.get_precision('Product Price History Card'))
    out_cost_total = fields.Float('Out cost total',
                                  digits=dp.get_precision('Product Price'))

    # End
    end_quantity = fields.Float('End quantity')
    # En la vista solo mostrar con la cantidad de dígitos de 'Product Price'
    end_cost = fields.Float(
        'End cost', digits=dp.get_precision('Product Price History Card'))
    end_cost_total = fields.Float('End cost total',
                                  digits=dp.get_precision('Product Price'))

    io = fields.Char(string='I/O', size=5)

    @api.depends('move_line_id', 'date', 'set_date')
    def _compute_date_card(self):
        for i in self:
            i.datetime = i.set_date or i.date

    @api.depends('location_id', 'card_location_id', 'datetime')
    def _compute_standard_price(self):
        for i in self:
            i.standard_price = self.env['product.price.history.card'].search(
                [('location_id', '=', i.card_location_id.id),
                 ('product_id', '=', i.product_id.id),
                 ('lot_id', '=', i.lot_id.id),
                 ('package_id', '=', i.package_id.id),
                 ('datetime', '<=', i.date)],
                order='datetime desc,id desc',
                limit=1).cost
예제 #23
0
파일: hr_expense.py 프로젝트: westlyou/odoo
class HrExpenseSheet(models.Model):

    _name = "hr.expense.sheet"
    _inherit = ['mail.thread']
    _description = "Expense Report"
    _order = "accounting_date desc, id desc"

    name = fields.Char(string='Expense Report Summary', required=True)
    expense_line_ids = fields.One2many('hr.expense',
                                       'sheet_id',
                                       string='Expense Lines',
                                       states={
                                           'done': [('readonly', True)],
                                           'post': [('readonly', True)]
                                       },
                                       copy=False)
    state = fields.Selection([('submit', 'Submitted'), ('approve', 'Approved'),
                              ('post', 'Posted'), ('done', 'Paid'),
                              ('cancel', 'Refused')],
                             string='Status',
                             index=True,
                             readonly=True,
                             track_visibility='onchange',
                             copy=False,
                             default='submit',
                             required=True,
                             help='Expense Report State')
    employee_id = fields.Many2one(
        'hr.employee',
        string="Employee",
        required=True,
        readonly=True,
        states={'submit': [('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="Payment By")
    responsible_id = fields.Many2one('res.users',
                                     'Validation By',
                                     readonly=True,
                                     copy=False,
                                     states={
                                         'submit': [('readonly', False)],
                                         'submit': [('readonly', False)]
                                     })
    total_amount = fields.Float(string='Total Amount',
                                store=True,
                                compute='_compute_amount',
                                digits=dp.get_precision('Account'))
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 readonly=True,
                                 states={'submit': [('readonly', False)]},
                                 default=lambda self: self.env.user.company_id)
    currency_id = fields.Many2one(
        'res.currency',
        string='Currency',
        readonly=True,
        states={'submit': [('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=lambda self: self.env['ir.model.data'].
        xmlid_to_object('hr_expense.hr_expense_account_journal') or self.env[
            'account.journal'].search([('type', '=', 'purchase')], limit=1),
        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=lambda self: self.env['account.journal'].search(
            [('type', 'in', ['case', 'bank'])], limit=1),
        help="The payment method used when the expense is paid by the company."
    )
    accounting_date = fields.Date(string="Accounting 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)]
                                    })

    @api.multi
    def check_consistency(self):
        if any(sheet.employee_id != self[0].employee_id for sheet in self):
            raise UserError(_("Expenses must belong to the same Employee."))

        expense_lines = self.mapped('expense_line_ids')
        if expense_lines and any(
                expense.payment_mode != expense_lines[0].payment_mode
                for expense in expense_lines):
            raise UserError(
                _("Expenses must have been paid by the same entity (Company or employee)"
                  ))

    @api.model
    def create(self, vals):
        sheet = super(HrExpenseSheet, self).create(vals)
        self.check_consistency()
        if vals.get('employee_id'):
            sheet._add_followers()
        return sheet

    @api.multi
    def write(self, vals):
        res = super(HrExpenseSheet, self).write(vals)
        self.check_consistency()
        if vals.get('employee_id'):
            self._add_followers()
        return res

    @api.multi
    def unlink(self):
        for expense in self:
            if expense.state == "post":
                raise UserError(_("You cannot delete a posted expense."))
        super(HrExpenseSheet, self).unlink()

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

    @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 == 'submit':
            return 'hr_expense.mt_expense_confirmed'
        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 _add_followers(self):
        user_ids = []
        employee = self.employee_id
        if employee.user_id:
            user_ids.append(employee.user_id.id)
        if employee.parent_id:
            user_ids.append(employee.parent_id.user_id.id)
        if employee.department_id and employee.department_id.manager_id and employee.parent_id != employee.department_id.manager_id:
            user_ids.append(employee.department_id.manager_id.user_id.id)
        self.message_subscribe_users(user_ids=user_ids)

    @api.onchange('employee_id')
    def _onchange_employee_id(self):
        self.address_id = self.employee_id.address_home_id
        self.department_id = self.employee_id.department_id

    @api.one
    @api.depends('expense_line_ids', 'expense_line_ids.total_amount',
                 'expense_line_ids.currency_id')
    def _compute_amount(self):
        if len(self.expense_line_ids.mapped('currency_id')) < 2:
            self.total_amount = sum(
                self.expense_line_ids.mapped('total_amount'))
        else:
            self.total_amount = 0.0

    # FIXME: A 4 command is missing to explicitly declare the one2many relation
    # between the sheet and the lines when using 'default_expense_line_ids':[ids]
    # in the context. A fix from chm-odoo should come since
    # several saas versions but sadly I had to add this hack to avoid this
    # issue
    @api.model
    def _add_missing_default_values(self, values):
        values = super(HrExpenseSheet,
                       self)._add_missing_default_values(values)
        if self.env.context.get('default_expense_line_ids', False):
            lines_to_add = []
            for line in values.get('expense_line_ids', []):
                if line[0] == 1:
                    lines_to_add.append([4, line[1], False])
            values[
                'expense_line_ids'] = lines_to_add + values['expense_line_ids']
        return values

    @api.one
    def _compute_attachment_number(self):
        self.attachment_number = sum(
            self.expense_line_ids.mapped('attachment_number'))

    @api.multi
    def refuse_expenses(self, reason):
        self.write({'state': 'cancel'})
        for sheet in self:
            body = (_(
                "Your Expense %s has been refused.<br/><ul class=o_timeline_tracking_value_list><li>Reason<span> : </span><span class=o_timeline_tracking_value>%s</span></li></ul>"
            ) % (sheet.name, reason))
            sheet.message_post(body=body)

    @api.multi
    def approve_expense_sheets(self):
        self.write({'state': 'approve', 'responsible_id': self.env.user.id})

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

    @api.multi
    def reset_expense_sheets(self):
        return self.write({'state': 'submit'})

    @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'})
        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
        }
        return res

    @api.one
    @api.constrains('expense_line_ids')
    def _check_employee(self):
        employee_ids = self.expense_line_ids.mapped('employee_id')
        if len(employee_ids) > 1 or (len(employee_ids) == 1
                                     and employee_ids != self.employee_id):
            raise ValidationError(
                _('You cannot add expense lines of another employee.'))
예제 #24
0
class BuyOrder(models.Model):
    _name = "buy.order"
    _inherit = ['mail.thread']
    _description = u"购货订单"
    _order = 'date desc, id desc'

    @api.one
    @api.depends('line_ids.subtotal', 'discount_amount')
    def _compute_amount(self):
        '''当订单行和优惠金额改变时,改变优惠后金额'''
        total = sum(line.subtotal for line in self.line_ids)
        self.amount = total - self.discount_amount

    @api.one
    @api.depends('line_ids.quantity', 'line_ids.quantity_in')
    def _get_buy_goods_state(self):
        '''返回收货状态'''
        if all(line.quantity_in == 0 for line in self.line_ids):
            self.goods_state = u'未入库'
        elif any(line.quantity > line.quantity_in for line in self.line_ids):
            self.goods_state = u'部分入库'
        else:
            self.goods_state = u'全部入库'

    @api.model
    def _default_warehouse_dest_impl(self):
        if self.env.context.get('warehouse_dest_type'):
            return self.env['warehouse'].get_warehouse_by_type(
                self.env.context.get('warehouse_dest_type'))

    @api.model
    def _default_warehouse_dest(self):
        '''获取默认调入仓库'''
        return self._default_warehouse_dest_impl()

    @api.one
    def _get_paid_amount(self):
        '''计算购货订单付款/退款状态'''
        receipts = self.env['buy.receipt'].search([('order_id', '=', self.id)])
        money_order_rows = self.env['money.order'].search([
            ('buy_id', '=', self.id), ('reconciled', '=', 0),
            ('state', '=', 'done')
        ])
        self.paid_amount = sum([receipt.invoice_id.reconciled for receipt in receipts]) +\
            sum([order_row.amount for order_row in money_order_rows])

    @api.depends('receipt_ids')
    def _compute_receipt(self):
        for order in self:
            order.receipt_count = len(order.receipt_ids.ids)

    @api.depends('receipt_ids')
    def _compute_invoice(self):
        for order in self:
            order.invoice_ids = order.receipt_ids.mapped('invoice_id')
            order.invoice_count = len(order.invoice_ids.ids)

    @api.one
    @api.depends('partner_id')
    def _compute_currency_id(self):
        """
        计算货币
        :return:
        """
        self.currency_id = self.partner_id.s_category_id.account_id.currency_id.id or self.partner_id.c_category_id.account_id.currency_id.id

    partner_id = fields.Many2one('partner',
                                 u'供应商',
                                 states=READONLY_STATES,
                                 ondelete='restrict',
                                 help=u'供应商')
    date = fields.Date(u'单据日期',
                       states=READONLY_STATES,
                       default=lambda self: fields.Date.context_today(self),
                       index=True,
                       copy=False,
                       help=u"默认是订单创建日期")
    planned_date = fields.Date(
        u'要求交货日期',
        states=READONLY_STATES,
        default=lambda self: fields.Date.context_today(self),
        index=True,
        copy=False,
        help=u"订单的要求交货日期")
    name = fields.Char(u'单据编号',
                       index=True,
                       copy=False,
                       help=u"购货订单的唯一编号,当创建时它会自动生成下一个编号。")
    type = fields.Selection([('buy', u'购货'), ('return', u'退货')],
                            u'类型',
                            default='buy',
                            states=READONLY_STATES,
                            help=u'购货订单的类型,分为购货或退货')
    ref = fields.Char(u'供应商订单号')
    warehouse_dest_id = fields.Many2one('warehouse',
                                        u'调入仓库',
                                        required=True,
                                        default=_default_warehouse_dest,
                                        ondelete='restrict',
                                        states=READONLY_STATES,
                                        help=u'将商品调入到该仓库')
    invoice_by_receipt = fields.Boolean(
        string=u"按收货结算",
        default=True,
        help=u'如未勾选此项,可在资金行里输入付款金额,订单保存后,采购人员可以单击资金行上的【确认】按钮。')
    line_ids = fields.One2many('buy.order.line',
                               'order_id',
                               u'购货订单行',
                               states=READONLY_STATES,
                               copy=True,
                               help=u'购货订单的明细行,不能为空')
    note = fields.Text(u'备注', help=u'单据备注')
    discount_rate = fields.Float(u'优惠率(%)',
                                 states=READONLY_STATES,
                                 digits=dp.get_precision('Amount'),
                                 help=u'整单优惠率')
    discount_amount = fields.Float(u'抹零',
                                   states=READONLY_STATES,
                                   track_visibility='always',
                                   digits=dp.get_precision('Amount'),
                                   help=u'整单优惠金额,可由优惠率自动计算出来,也可手动输入')
    amount = fields.Float(u'优惠后金额',
                          store=True,
                          compute='_compute_amount',
                          track_visibility='always',
                          digits=dp.get_precision('Amount'),
                          help=u'总金额减去优惠金额')
    prepayment = fields.Float(u'预付款',
                              states=READONLY_STATES,
                              digits=dp.get_precision('Amount'),
                              help=u'输入预付款审核购货订单,会产生一张付款单')
    bank_account_id = fields.Many2one('bank.account',
                                      u'结算账户',
                                      ondelete='restrict',
                                      help=u'用来核算和监督企业与其他单位或个人之间的债权债务的结算情况')
    approve_uid = fields.Many2one('res.users',
                                  u'审核人',
                                  copy=False,
                                  ondelete='restrict',
                                  help=u'审核单据的人')
    state = fields.Selection(BUY_ORDER_STATES,
                             u'审核状态',
                             readonly=True,
                             help=u"购货订单的审核状态",
                             index=True,
                             copy=False,
                             default='draft')
    goods_state = fields.Char(u'收货状态',
                              compute=_get_buy_goods_state,
                              default=u'未入库',
                              store=True,
                              help=u"购货订单的收货状态",
                              index=True,
                              copy=False)
    cancelled = fields.Boolean(u'已终止', help=u'该单据是否已终止')
    pay_ids = fields.One2many("payment.plan",
                              "buy_id",
                              string=u"付款计划",
                              help=u'分批付款时使用付款计划')
    goods_id = fields.Many2one('goods',
                               related='line_ids.goods_id',
                               string=u'商品')
    receipt_ids = fields.One2many('buy.receipt',
                                  'order_id',
                                  string='Receptions',
                                  copy=False)
    receipt_count = fields.Integer(compute='_compute_receipt',
                                   string='Receptions Count',
                                   default=0)
    invoice_ids = fields.One2many('money.invoice',
                                  compute='_compute_invoice',
                                  string='Invoices')
    invoice_count = fields.Integer(compute='_compute_invoice',
                                   string='Invoices Count',
                                   default=0)
    currency_id = fields.Many2one('res.currency',
                                  u'外币币别',
                                  compute='_compute_currency_id',
                                  store=True,
                                  help=u'外币币别')
    user_id = fields.Many2one(
        'res.users',
        u'经办人',
        ondelete='restrict',
        states=READONLY_STATES,
        default=lambda self: self.env.user,
        help=u'单据经办人',
    )
    company_id = fields.Many2one(
        'res.company',
        string=u'公司',
        change_default=True,
        default=lambda self: self.env['res.company']._company_default_get())
    paid_amount = fields.Float(u'已付金额',
                               compute=_get_paid_amount,
                               readonly=True)

    @api.onchange('discount_rate', 'line_ids')
    def onchange_discount_rate(self):
        '''当优惠率或购货订单行发生变化时,单据优惠金额发生变化'''
        total = sum(line.subtotal for line in self.line_ids)
        self.discount_amount = total * self.discount_rate * 0.01

    @api.onchange('partner_id')
    def onchange_partner_id(self):
        if self.partner_id:
            for line in self.line_ids:
                if line.goods_id.tax_rate and self.partner_id.tax_rate:
                    if line.goods_id.tax_rate >= self.partner_id.tax_rate:
                        line.tax_rate = self.partner_id.tax_rate
                    else:
                        line.tax_rate = line.goods_id.tax_rate
                elif line.goods_id.tax_rate and not self.partner_id.tax_rate:
                    line.tax_rate = line.goods_id.tax_rate
                elif not line.goods_id.tax_rate and self.partner_id.tax_rate:
                    line.tax_rate = self.partner_id.tax_rate
                else:
                    line.tax_rate = self.env.user.company_id.import_tax_rate

    @api.multi
    def unlink(self):
        for order in self:
            if order.state == 'done':
                raise UserError(u'不能删除已审核的单据(%s)' % order.name)

        return super(BuyOrder, self).unlink()

    def _get_vals(self):
        '''返回创建 money_order 时所需数据'''
        flag = (self.type == 'buy' and 1 or -1)  # 用来标志入库或退货
        amount = flag * self.amount
        this_reconcile = flag * self.prepayment
        money_lines = [{
            'bank_id': self.bank_account_id.id,
            'amount': this_reconcile,
        }]
        return {
            'partner_id': self.partner_id.id,
            'bank_name': self.partner_id.bank_name,
            'bank_num': self.partner_id.bank_num,
            'date': fields.Date.context_today(self),
            'line_ids': [(0, 0, line) for line in money_lines],
            'amount': amount,
            'reconciled': this_reconcile,
            'to_reconcile': amount,
            'state': 'draft',
            'origin_name': self.name,
            'buy_id': self.id,
        }

    @api.one
    def generate_payment_order(self):
        '''由购货订单生成付款单'''
        # 入库单/退货单
        if self.prepayment:
            money_order = self.with_context(
                type='pay').env['money.order'].create(self._get_vals())
            return money_order

    @api.one
    def buy_order_done(self):
        '''审核购货订单'''
        if self.state == 'done':
            raise UserError(u'请不要重复审核')
        if not self.line_ids:
            raise UserError(u'请输入商品明细行')
        for line in self.line_ids:
            if line.quantity <= 0 or line.price_taxed < 0:
                raise UserError(u'商品 %s 的数量和含税单价不能小于0' % line.goods_id.name)
            if line.tax_amount > 0 and self.currency_id:
                raise UserError(u'外贸免税')
        if not self.bank_account_id and self.prepayment:
            raise UserError(u'预付款不为空时,请选择结算账户')
        # 采购预付款生成付款单
        self.generate_payment_order()
        self.buy_generate_receipt()
        self.state = 'done'
        self.approve_uid = self._uid

    @api.one
    def buy_order_draft(self):
        '''反审核购货订单'''
        if self.state == 'draft':
            raise UserError(u'请不要重复反审核!')
        if self.goods_state != u'未入库':
            raise UserError(u'该购货订单已经收货,不能反审核!')
        # 查找产生的入库单并删除
        receipt = self.env['buy.receipt'].search([('order_id', '=', self.name)
                                                  ])
        receipt.unlink()
        # 查找产生的付款单并反审核,删除
        money_order = self.env['money.order'].search([('origin_name', '=',
                                                       self.name)])
        if money_order:
            if money_order.state == 'done':
                money_order.money_order_draft()
            money_order.unlink()
        self.state = 'draft'
        self.approve_uid = ''

    @api.one
    def get_receipt_line(self, line, single=False):
        '''返回采购入库/退货单行'''
        qty = 0
        discount_amount = 0
        if single:
            qty = 1
            discount_amount = (line.discount_amount /
                               ((line.quantity - line.quantity_in) or 1))
        else:
            qty = line.quantity - line.quantity_in
            discount_amount = line.discount_amount
        return {
            'type': self.type == 'buy' and 'in' or 'out',
            'buy_line_id': line.id,
            'goods_id': line.goods_id.id,
            'attribute_id': line.attribute_id.id,
            'uos_id': line.goods_id.uos_id.id,
            'goods_qty': qty,
            'uom_id': line.uom_id.id,
            'cost_unit': line.price,
            'price_taxed': line.price_taxed,
            'discount_rate': line.discount_rate,
            'discount_amount': discount_amount,
            'tax_rate': line.tax_rate,
            'note': line.note or '',
        }

    def _generate_receipt(self, receipt_line):
        '''根据明细行生成入库单或退货单'''
        # 如果退货,warehouse_dest_id,warehouse_id要调换
        warehouse = (self.type == 'buy'
                     and self.env.ref("warehouse.warehouse_supplier")
                     or self.warehouse_dest_id)
        warehouse_dest = (self.type == 'buy' and self.warehouse_dest_id
                          or self.env.ref("warehouse.warehouse_supplier"))
        rec = (self.type == 'buy' and self.with_context(is_return=False)
               or self.with_context(is_return=True))
        receipt_id = rec.env['buy.receipt'].create({
            'partner_id':
            self.partner_id.id,
            'warehouse_id':
            warehouse.id,
            'warehouse_dest_id':
            warehouse_dest.id,
            'date':
            self.planned_date,
            'date_due':
            self.planned_date,
            'order_id':
            self.id,
            'origin':
            'buy.receipt',
            'note':
            self.note,
            'discount_rate':
            self.discount_rate,
            'discount_amount':
            self.discount_amount,
            'invoice_by_receipt':
            self.invoice_by_receipt,
            'currency_id':
            self.currency_id.id,
        })
        if self.type == 'buy':
            receipt_id.write(
                {'line_in_ids': [(0, 0, line[0]) for line in receipt_line]})
        else:
            receipt_id.write(
                {'line_out_ids': [(0, 0, line[0]) for line in receipt_line]})
        return receipt_id

    @api.one
    def buy_generate_receipt(self):
        '''由购货订单生成采购入库/退货单'''
        receipt_line = []  # 采购入库/退货单行

        for line in self.line_ids:
            # 如果订单部分入库,则点击此按钮时生成剩余数量的入库单
            to_in = line.quantity - line.quantity_in
            if to_in <= 0:
                continue
            if line.goods_id.force_batch_one:
                i = 0
                while i < to_in:
                    i += 1
                    receipt_line.append(
                        self.get_receipt_line(line, single=True))
            else:
                receipt_line.append(self.get_receipt_line(line, single=False))

        if not receipt_line:
            return {}
        receipt_id = self._generate_receipt(receipt_line)
        view_id = (self.type == 'buy'
                   and self.env.ref('buy.buy_receipt_form').id
                   or self.env.ref('buy.buy_return_form').id)
        name = (self.type == 'buy' and u'采购入库单' or u'采购退货单')

        return {
            'name': name,
            'view_type': 'form',
            'view_mode': 'form',
            'view_id': False,
            'views': [(view_id, 'form')],
            'res_model': 'buy.receipt',
            'type': 'ir.actions.act_window',
            'domain': [('id', '=', receipt_id)],
            'target': 'current',
        }

    @api.multi
    def action_view_receipt(self):
        '''
        This function returns an action that display existing picking orders of given purchase order ids.
        When only one found, show the picking immediately.
        '''

        self.ensure_one()
        name = (self.type == 'buy' and u'采购入库单' or u'采购退货单')
        action = {
            'name': name,
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'buy.receipt',
            'view_id': False,
            'target': 'current',
        }

        #receipt_ids = sum([order.receipt_ids.ids for order in self], [])
        receipt_ids = self.receipt_ids.ids
        # choose the view_mode accordingly
        if len(receipt_ids) > 1:
            action['domain'] = "[('id','in',[" + \
                ','.join(map(str, receipt_ids)) + "])]"
            action['view_mode'] = 'tree,form'
        elif len(receipt_ids) == 1:
            view_id = (self.type == 'buy'
                       and self.env.ref('buy.buy_receipt_form').id
                       or self.env.ref('buy.buy_return_form').id)
            action['views'] = [(view_id, 'form')]
            action['res_id'] = receipt_ids and receipt_ids[0] or False
        return action

    @api.multi
    def action_view_invoice(self):
        '''
        This function returns an action that display existing invoices of given purchase order ids( linked/computed via buy.receipt).
        When only one found, show the invoice immediately.
        '''

        self.ensure_one()
        if self.invoice_count == 0:
            return False
        action = {
            'name': u'结算单(供应商发票)',
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'money.invoice',
            'view_id': False,
            'target': 'current',
        }
        invoice_ids = self.invoice_ids.ids
        # choose the view_mode accordingly
        if len(invoice_ids) > 1:
            action['domain'] = "[('id','in',[" + \
                ','.join(map(str, invoice_ids)) + "])]"
            action['view_mode'] = 'tree'
        elif len(invoice_ids) == 1:
            action['views'] = [(False, 'form')]
            action['res_id'] = invoice_ids and invoice_ids[0] or False
        return action
예제 #25
0
class SaleOrder(models.Model):
    _inherit = "sale.order"

    port_loading = fields.Many2one('pwk.port', 'Port Of Loading')
    port_discharge = fields.Many2one('pwk.port', 'Port Of Discharge')
    destination_id = fields.Many2one('pwk.destination', 'Destination')
    method_payment_id = fields.Many2one('pwk.method.payment',
                                        'Method of Payment')
    po_number = fields.Char('PO Buyer No.')
    quantity = fields.Char('Quantity')
    thickness = fields.Char('Thickness')
    nama_terang = fields.Selection([('Andreas Hermawan', 'Andreas Hermawan'),
                                    ('Adi Widiawan', 'Adi Widiawan')],
                                   string='Nama Terang')
    beneficiary = fields.Text('Beneficiary')
    marking = fields.Char('Marking')
    packing = fields.Char('Packing')
    insurance = fields.Char('Insurance')
    delivery_date = fields.Date('Est. Delivery Date')
    journal_id = fields.Many2one('account.journal',
                                 string='Bank Account',
                                 domain="[('type','=','bank')]")
    incoterm_id = fields.Many2one('account.incoterms',
                                  string='Delivery Method')
    mc_id = fields.Many2one('sale.mc', string='Moisture Content')
    discrepancy_id = fields.Many2one('sale.discrepancy', string='Discrepancy')
    date_order = fields.Date(string='Order Date',
                             required=True,
                             readonly=True,
                             index=True,
                             states={
                                 'draft': [('readonly', False)],
                                 'sent': [('readonly', False)]
                             },
                             copy=False,
                             default=fields.Date.today())
    office_selection = fields.Selection([('Temanggung', 'Temanggung'),
                                         ('Jakarta', 'Jakarta')],
                                        string="Lokasi",
                                        default="Temanggung",
                                        track_visibility="always")
    certificate_id = fields.Many2one('pwk.certificate', 'Certificate')
    packing_id = fields.Many2one('pwk.packing', 'Packing')
    is_logo = fields.Boolean('Show Legal Logo', default=True)
    contract_type = fields.Selection(
        [('Lokal', 'Lokal'), ('Export', 'Export'),
         ('Waste Rotary', 'Waste Rotary'),
         ('Waste Pabrik PPN', 'Waste Pabrik PPN'),
         ('Waste Pabrik Non-PPN', 'Waste Pabrik Non-PPN')],
        string="Contract Type",
        default="Lokal")
    amount_total_terbilang = fields.Char(compute="_get_terbilang",
                                         string='Amount Total Terbilang')
    amount_total_terbilang_en = fields.Char(
        compute="_get_terbilang_english",
        string='Amount Total Terbilang English')
    attn = fields.Char('Attn')
    stempel_ids = fields.One2many('sale.order.stempel', 'reference', 'Stempel')
    marking_ids = fields.One2many('sale.order.marking', 'reference', 'Marking')
    sticker_ids = fields.One2many('sale.order.sticker', 'reference', 'Sticker')
    state = fields.Selection([
        ('draft', 'Quotation'),
        ('sent', 'Quotation Sent'),
        ('Sales Contract', 'Sales Contract'),
        ('sale', 'Sales Order'),
        ('done', 'Locked'),
        ('cancel', 'Cancelled'),
    ],
                             string='Status',
                             readonly=True,
                             copy=False,
                             index=True,
                             track_visibility='onchange',
                             track_sequence=3,
                             default='draft')
    total_volume = fields.Float(compute="_get_total",
                                string="Total Volume",
                                digits=dp.get_precision('FourDecimal'))
    total_qty = fields.Float(compute="_get_total",
                             string="Total Qty",
                             digits=dp.get_precision('TwoDecimal'))
    formula_type = fields.Selection([('Volume', 'Volume'), ('PCS', 'PCS')],
                                    string="Price Formula",
                                    default="PCS")
    number_contract = fields.Char(compute="_get_contract_no",
                                  string="Sales Contract No.")
    job_order_status = fields.Char(string='Job Order Status',
                                   default='Not Ready')
    is_changed = fields.Boolean('Changed')

    @api.multi
    def button_reload_crate(self):
        for res in self:
            for line in res.order_line:
                if line.container_ids:
                    for container in line.container_ids:
                        container.unlink()

                number = 1
                while number <= line.crate_number:
                    self.env['sale.order.line.container'].create({
                        'reference':
                        line.id,
                        'position_id':
                        line.crate_position_id.id,
                        'pallet_id':
                        line.crate_pallet_id.id,
                        'strapping_id':
                        line.crate_strapping_id.id,
                        'qty':
                        line.crate_qty_each,
                        'number':
                        number
                    })

                    number += 1

    @api.multi
    def button_change(self):
        for res in self:
            if res.is_changed:
                for line in res.order_line:
                    line.write({'is_changed': False})
            else:
                for line in res.order_line:
                    line.write({'is_changed': True})

    def get_sequence(self, name=False, obj=False, pref=False, context=None):
        sequence_id = self.env['ir.sequence'].search([
            ('name', '=', name), ('code', '=', obj),
            ('suffix', '=', '/' + pref + '-PWK/%(month)s-%(year)s')
        ])
        if not sequence_id:
            sequence_id = self.env['ir.sequence'].sudo().create({
                'name':
                name,
                'code':
                obj,
                'implementation':
                'no_gap',
                'suffix':
                '/' + pref + '-PWK/%(month)s-%(year)s',
                'padding':
                3
            })
        return sequence_id.next_by_id()

    @api.model
    def create(self, vals):
        if vals.get('name', _('New')) == _('New'):
            partner_code = self.env['res.partner'].browse(
                vals['partner_id']).code
            vals['name'] = self.get_sequence('Sales Order', 'sale.order',
                                             '%s' % partner_code)

        # Makes sure partner_invoice_id', 'partner_shipping_id' and 'pricelist_id' are defined
        if any(f not in vals for f in
               ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id']):
            partner = self.env['res.partner'].browse(vals.get('partner_id'))
            addr = partner.address_get(['delivery', 'invoice'])
            vals['partner_invoice_id'] = vals.setdefault(
                'partner_invoice_id', addr['invoice'])
            vals['partner_shipping_id'] = vals.setdefault(
                'partner_shipping_id', addr['delivery'])
            vals['pricelist_id'] = vals.setdefault(
                'pricelist_id', partner.property_product_pricelist
                and partner.property_product_pricelist.id)
        result = super(SaleOrder, self).create(vals)
        return result

    # @api.multi
    # def action_close_order(self):
    #     for res in self:
    #         if res.is_closed:

    def _get_contract_no(self):
        for res in self:
            number_contract = ''

            if res.name:
                number_contract = res.name.replace('SO', 'SC')

            res.number_contract = res.name

    @api.depends('order_line.volume', 'order_line.product_uom_qty')
    def _get_total(self):
        for res in self:
            total_qty = 0
            total_volume = 0

            if res.order_line:
                for line in res.order_line:
                    total_qty += line.product_uom_qty
                    total_volume += line.volume

            res.total_qty = total_qty
            res.total_volume = total_volume

    def terbilang(self, satuan):
        huruf = [
            "", "Satu", "Dua", "Tiga", "Empat", "Lima", "Enam", "Tujuh",
            "Delapan", "Sembilan", "Sepuluh", "Sebelas"
        ]
        # huruf = ["","One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten","Eleven","Twelve"]
        hasil = ""
        if satuan < 12:
            hasil = hasil + huruf[int(satuan)]
        elif satuan < 20:
            hasil = hasil + self.terbilang(satuan - 10) + " Belas"
        elif satuan < 100:
            hasil = hasil + self.terbilang(
                satuan / 10) + " Puluh " + self.terbilang(satuan % 10)
        elif satuan < 200:
            hasil = hasil + "Seratus " + self.terbilang(satuan - 100)
        elif satuan < 1000:
            hasil = hasil + self.terbilang(
                satuan / 100) + " Ratus " + self.terbilang(satuan % 100)
        elif satuan < 2000:
            hasil = hasil + "Seribu " + self.terbilang(satuan - 1000)
        elif satuan < 1000000:
            hasil = hasil + self.terbilang(
                satuan / 1000) + " Ribu " + self.terbilang(satuan % 1000)
        elif satuan < 1000000000:
            hasil = hasil + self.terbilang(
                satuan / 1000000) + " Juta " + self.terbilang(satuan % 1000000)
        elif satuan < 1000000000000:
            hasil = hasil + self.terbilang(
                satuan / 1000000000) + " Milyar " + self.terbilang(
                    satuan % 1000000000)
        elif satuan >= 1000000000000:
            hasil = "Angka terlalu besar, harus kurang dari 1 Trilyun!"
        return hasil

    @api.depends('amount_total')
    def _get_terbilang(self):
        for res in self:
            amount = res.terbilang(res.amount_total)
            res.amount_total_terbilang = amount + " Rupiah"

    @api.depends('amount_total')
    def _get_terbilang_english(self):
        for res in self:
            new_amount = ''

            amount = num2words(res.amount_total)
            text_ids = amount.split(' ')
            for text in text_ids:
                if new_amount:
                    new_amount += (" " + text.capitalize())
                else:
                    new_amount += (text.capitalize())

            res.amount_total_terbilang_en = new_amount + " Dollars"

    @api.multi
    def print_sale_contract(self):
        return self.env.ref('v12_pwk.sale_contract').report_action(self)

    @api.multi
    def print_lampiran_sale_order(self):
        return self.env.ref('v12_pwk.lampiran_sale_order').report_action(self)

    @api.multi
    def print_sale_order(self):
        return self.env.ref('v12_pwk.sale_order').report_action(self)

    @api.multi
    def button_contract(self):
        for res in self:
            res.write({'state': 'Sales Contract'})
예제 #26
0
class BuyOrderLine(models.Model):
    _name = 'buy.order.line'
    _description = u'购货订单明细'

    @api.one
    @api.depends('goods_id')
    def _compute_using_attribute(self):
        '''返回订单行中商品是否使用属性'''
        self.using_attribute = self.goods_id.attribute_ids and True or False

    @api.one
    @api.depends('quantity', 'price_taxed', 'discount_amount', 'tax_rate')
    def _compute_all_amount(self):
        '''当订单行的数量、含税单价、折扣额、税率改变时,改变购货金额、税额、价税合计'''
        if self.tax_rate > 100:
            raise UserError(u'税率不能输入超过100的数')
        if self.tax_rate < 0:
            raise UserError(u'税率不能输入负数')
        if self.order_id.currency_id.id == self.env.user.company_id.currency_id.id:
            # 单据上外币是公司本位币
            self.price = self.price_taxed / (1 + self.tax_rate * 0.01)  # 不含税单价
            self.subtotal = self.price_taxed * self.quantity - self.discount_amount  # 价税合计
            self.tax_amount = self.subtotal / \
                (100 + self.tax_rate) * self.tax_rate  # 税额
            self.amount = self.subtotal - self.tax_amount  # 金额
        else:
            # 非公司本位币
            rate_silent = (self.env['res.currency'].get_rate_silent(
                self.order_id.date, self.order_id.currency_id.id) or 1)
            currency_amount = self.quantity * self.price_taxed - self.discount_amount
            self.price = self.price_taxed * \
                rate_silent / (1 + self.tax_rate * 0.01)
            self.subtotal = (self.price_taxed * self.quantity -
                             self.discount_amount) * rate_silent  # 价税合计
            self.tax_amount = self.subtotal / \
                (100 + self.tax_rate) * self.tax_rate  # 税额
            self.amount = self.subtotal - self.tax_amount  # 本位币金额
            self.currency_amount = currency_amount  # 外币金额

    @api.one
    def _inverse_price(self):
        '''由不含税价反算含税价,保存时生效'''
        self.price_taxed = self.price * (1 + self.tax_rate * 0.01)

    @api.onchange('price', 'tax_rate')
    def onchange_price(self):
        '''当订单行的不含税单价改变时,改变含税单价'''
        price = self.price_taxed / (1 + self.tax_rate * 0.01)  # 不含税单价
        decimal = self.env.ref('core.decimal_price')
        if float_compare(price, self.price,
                         precision_digits=decimal.digits) != 0:
            self.price_taxed = self.price * (1 + self.tax_rate * 0.01)

    order_id = fields.Many2one('buy.order',
                               u'订单编号',
                               index=True,
                               required=True,
                               ondelete='cascade',
                               help=u'关联订单的编号')
    goods_id = fields.Many2one('goods', u'商品', ondelete='restrict', help=u'商品')
    using_attribute = fields.Boolean(u'使用属性',
                                     compute=_compute_using_attribute,
                                     help=u'商品是否使用属性')
    attribute_id = fields.Many2one('attribute',
                                   u'属性',
                                   ondelete='restrict',
                                   domain="[('goods_id', '=', goods_id)]",
                                   help=u'商品的属性,当商品有属性时,该字段必输')
    uom_id = fields.Many2one('uom', u'单位', ondelete='restrict', help=u'商品计量单位')
    quantity = fields.Float(u'数量',
                            default=1,
                            required=True,
                            digits=dp.get_precision('Quantity'),
                            help=u'下单数量')
    quantity_in = fields.Float(u'已执行数量',
                               copy=False,
                               digits=dp.get_precision('Quantity'),
                               help=u'购货订单产生的入库单/退货单已执行数量')
    price = fields.Float(u'购货单价',
                         compute=_compute_all_amount,
                         inverse=_inverse_price,
                         store=True,
                         digits=dp.get_precision('Price'),
                         help=u'不含税单价,由含税单价计算得出')
    price_taxed = fields.Float(u'含税单价',
                               digits=dp.get_precision('Price'),
                               help=u'含税单价,取自商品成本或对应供应商的购货价')
    discount_rate = fields.Float(u'折扣率%', help=u'折扣率')
    discount_amount = fields.Float(u'折扣额',
                                   digits=dp.get_precision('Amount'),
                                   help=u'输入折扣率后自动计算得出,也可手动输入折扣额')
    amount = fields.Float(u'金额',
                          compute=_compute_all_amount,
                          store=True,
                          digits=dp.get_precision('Amount'),
                          help=u'金额  = 价税合计  - 税额')
    currency_amount = fields.Float(u'外币金额',
                                   compute=_compute_all_amount,
                                   store=True,
                                   digits=dp.get_precision('Amount'),
                                   help=u'外币金额')
    tax_rate = fields.Float(
        u'税率(%)',
        default=lambda self: self.env.user.company_id.import_tax_rate,
        help=u'默认值取公司进项税率')
    tax_amount = fields.Float(u'税额',
                              compute=_compute_all_amount,
                              store=True,
                              digits=dp.get_precision('Amount'),
                              help=u'由税率计算得出')
    subtotal = fields.Float(u'价税合计',
                            compute=_compute_all_amount,
                            store=True,
                            digits=dp.get_precision('Amount'),
                            help=u'含税单价 乘以 数量')
    note = fields.Char(u'备注', help=u'本行备注')
    # TODO:放到单独模块中 sell_to_buy many2one 到sell.order
    origin = fields.Char(u'销售单号', help=u'以销订购的销售订单号')
    company_id = fields.Many2one(
        'res.company',
        string=u'公司',
        change_default=True,
        default=lambda self: self.env['res.company']._company_default_get())

    @api.onchange('goods_id', 'quantity')
    def onchange_goods_id(self):
        '''当订单行的商品变化时,带出商品上的单位、成本价。
        在采购订单上选择供应商,自动带出供货价格,没有设置供货价的取成本价格。'''
        if not self.order_id.partner_id:
            raise UserError(u'请先选择一个供应商!')
        if self.goods_id:
            self.uom_id = self.goods_id.uom_id
            self.price_taxed = self.goods_id.cost
            for line in self.goods_id.vendor_ids:
                if line.vendor_id == self.order_id.partner_id \
                        and self.quantity >= line.min_qty:
                    self.price_taxed = line.price
                    break

            if self.goods_id.tax_rate and self.order_id.partner_id.tax_rate:
                if self.goods_id.tax_rate >= self.order_id.partner_id.tax_rate:
                    self.tax_rate = self.order_id.partner_id.tax_rate
                else:
                    self.tax_rate = self.goods_id.tax_rate
            elif self.goods_id.tax_rate and not self.order_id.partner_id.tax_rate:
                self.tax_rate = self.goods_id.tax_rate
            elif not self.goods_id.tax_rate and self.order_id.partner_id.tax_rate:
                self.tax_rate = self.order_id.partner_id.tax_rate
            else:
                self.tax_rate = self.env.user.company_id.import_tax_rate

    @api.onchange('quantity', 'price_taxed', 'discount_rate')
    def onchange_discount_rate(self):
        '''当数量、单价或优惠率发生变化时,优惠金额发生变化'''
        price = self.price_taxed / (1 + self.tax_rate * 0.01)
        self.discount_amount = (self.quantity * price * self.discount_rate *
                                0.01)
예제 #27
0
class SellOrderLine(models.Model):
    _name = 'sell.order.line'
    _description = u'销货订单明细'

    @api.one
    @api.depends('goods_id')
    def _compute_using_attribute(self):
        '''返回订单行中商品是否使用属性'''
        self.using_attribute = self.goods_id.attribute_ids and True or False

    @api.one
    @api.depends('quantity', 'price_taxed', 'discount_amount', 'tax_rate')
    def _compute_all_amount(self):
        '''当订单行的数量、含税单价、折扣额、税率改变时,改变销售金额、税额、价税合计'''
        if self.tax_rate > 100:
            raise UserError(u'税率不能输入超过100的数!\n输入税率:%s' % self.tax_rate)
        if self.tax_rate < 0:
            raise UserError(u'税率不能输入负数\n 输入税率:%s' % self.tax_rate)
        if self.order_id.currency_id.id == self.env.user.company_id.currency_id.id:
            self.subtotal = self.price_taxed * self.quantity - self.discount_amount  # 价税合计
            self.tax_amount = self.subtotal / \
                (100 + self.tax_rate) * self.tax_rate  # 税额
            self.amount = self.subtotal - self.tax_amount  # 金额
        else:
            rate_silent = self.env['res.currency'].get_rate_silent(
                self.order_id.date, self.order_id.currency_id.id) or 1
            currency_amount = self.quantity * self.price_taxed - self.discount_amount
            self.subtotal = (self.price_taxed * self.quantity -
                             self.discount_amount) * rate_silent  # 价税合计
            self.tax_amount = self.subtotal / \
                (100 + self.tax_rate) * self.tax_rate  # 税额
            self.amount = self.subtotal - self.tax_amount  # 本位币金额
            self.currency_amount = currency_amount  # 外币金额

    @api.onchange('price', 'tax_rate')
    def onchange_price(self):
        '''当订单行的不含税单价改变时,改变含税单价。
        如果将含税价改为99,则self.price计算出来为84.62,price=99/1.17,
        跟84.62保留相同位数比较时是相等的,这种情况则保留含税价不变,
        这样处理是为了使得修改含税价时不再重新计算含税价。
        '''
        price = self.price_taxed / (1 + self.tax_rate * 0.01)  # 不含税单价
        decimal = self.env.ref('core.decimal_price')
        if float_compare(price, self.price, precision_digits=decimal.digits) != 0:
            self.price_taxed = self.price * (1 + self.tax_rate * 0.01)

    order_id = fields.Many2one('sell.order', u'订单编号', index=True,
                               required=True, ondelete='cascade',
                               help=u'关联订单的编号')
    currency_amount = fields.Float(u'外币金额', compute=_compute_all_amount,
                                   store=True,
                                   digits=dp.get_precision('Amount'),
                                   help=u'外币金额')
    goods_id = fields.Many2one('goods',
                               u'商品',
                               required=True,
                               ondelete='restrict',
                               help=u'商品')
    using_attribute = fields.Boolean(u'使用属性', compute=_compute_using_attribute,
                                     help=u'商品是否使用属性')
    attribute_id = fields.Many2one('attribute', u'属性',
                                   ondelete='restrict',
                                   domain="[('goods_id', '=', goods_id)]",
                                   help=u'商品的属性,当商品有属性时,该字段必输')
    uom_id = fields.Many2one('uom', u'单位', ondelete='restrict',
                             help=u'商品计量单位')
    quantity = fields.Float(u'数量',
                            default=1,
                            required=True,
                            digits=dp.get_precision('Quantity'),
                            help=u'下单数量')
    quantity_out = fields.Float(u'已执行数量', copy=False,
                                digits=dp.get_precision('Quantity'),
                                help=u'销货订单产生的发货单/退货单已执行数量')
    price = fields.Float(u'销售单价',
                         store=True,
                         digits=dp.get_precision('Price'),
                         help=u'不含税单价,由含税单价计算得出')
    price_taxed = fields.Float(u'含税单价',
                               digits=dp.get_precision('Price'),
                               help=u'含税单价,取商品零售价')
    discount_rate = fields.Float(u'折扣率%',
                                 help=u'折扣率')
    discount_amount = fields.Float(u'折扣额',
                                   help=u'输入折扣率后自动计算得出,也可手动输入折扣额')
    amount = fields.Float(u'金额',
                          compute=_compute_all_amount,
                          store=True,
                          digits=dp.get_precision('Amount'),
                          help=u'金额  = 价税合计  - 税额')
    tax_rate = fields.Float(u'税率(%)',
                            help=u'税率')
    tax_amount = fields.Float(u'税额',
                              compute=_compute_all_amount,
                              store=True,
                              digits=dp.get_precision('Amount'),
                              help=u'税额')
    subtotal = fields.Float(u'价税合计',
                            compute=_compute_all_amount,
                            store=True,
                            digits=dp.get_precision('Amount'),
                            help=u'含税单价 乘以 数量')
    note = fields.Char(u'备注',
                       help=u'本行备注')
    company_id = fields.Many2one(
        'res.company',
        string=u'公司',
        change_default=True,
        default=lambda self: self.env['res.company']._company_default_get())

    @api.onchange('goods_id')
    def onchange_warehouse_id(self):
        '''当订单行的仓库变化时,带出定价策略中的折扣率'''
        if self.order_id.warehouse_id and self.goods_id:
            partner = self.order_id.partner_id
            warehouse = self.order_id.warehouse_id
            goods = self.goods_id
            date = self.order_id.date
            pricing = self.env['pricing'].get_pricing_id(
                partner, warehouse, goods, date)
            if pricing:
                self.discount_rate = pricing.discount_rate
            else:
                self.discount_rate = 0

    @api.multi
    @api.onchange('goods_id')
    def onchange_goods_id(self):
        '''当订单行的商品变化时,带出商品上的单位、默认仓库、价格、税率'''
        if self.goods_id:
            self.uom_id = self.goods_id.uom_id
            self.price_taxed = self.goods_id.price
            self.tax_rate = self.goods_id.get_tax_rate(self.goods_id, self.order_id.partner_id, 'sell')

    @api.onchange('quantity', 'price_taxed', 'discount_rate')
    def onchange_discount_rate(self):
        '''当数量、单价或优惠率发生变化时,优惠金额发生变化'''
        self.price = self.price_taxed / (1 + self.tax_rate * 0.01)
        self.discount_amount = self.quantity * self.price \
            * self.discount_rate * 0.01
예제 #28
0
class PurchaseOrderLine(models.Model):
    _inherit = 'purchase.order.line'
    modelo = fields.Many2one("modelo.toyosa", "Modelo")
    katashiki = fields.Many2one("katashiki.toyosa", u"Código modelo")
    colorinterno = fields.Many2one("color.interno", string="Color Interno")
    colorexterno = fields.Many2one("color.externo", string="Color Externo")
    anio = fields.Many2one("anio.toyosa", string=u"Año Modelo")
    edicion = fields.Char("ED")
    date_neumatico = fields.Datetime(string=u'Fecha vencimiento',
                                     required=True,
                                     index=True,
                                     default=fields.Datetime.now)
    price_transporte = fields.Float(string='Precio transporte y seguro',
                                    required=True,
                                    digits=dp.get_precision('Product Price'),
                                    default=0.0)
    price_fabrica = fields.Float(string='Precio fabrica',
                                 required=True,
                                 digits=dp.get_precision('Product Price'),
                                 default=0.0)

    price_flete = fields.Float("Costo Flete")
    price_seguro = fields.Float("Costo Seguro")

    # Filtrar productos en funcion de los que se selecciona en la orden
    @api.onchange('modelo', 'katashiki')
    def onchange_modelo_katashiki(self):
        res = {}
        res.setdefault('domain', {})
        # Katashiki es el 'Código Modelo'
        if self.modelo and not self.katashiki:
            res['domain'] = {
                'product_id': [('modelo', '=', self.modelo.id),
                               ('tracking', '=', 'serial')],
                'katashiki': [('modelo', '=', self.modelo.id)]
            }
            return res
        elif self.katashiki and not self.modelo:
            res['domain'] = {
                'product_id': [('katashiki', '=', self.katashiki.id),
                               ('tracking', '=', 'serial')]
            }
            return res
        elif self.modelo and self.katashiki:
            res['domain'] = {
                'product_id': [('modelo', '=', self.modelo.id),
                               ('katashiki', '=', self.katashiki.id),
                               ('tracking', '=', 'serial')]
            }
            return res
        else:
            res['domain'] = {
                'product_id': [('active', '=', True),
                               ('purchase_ok', '=', True),
                               ('tracking', '=', 'serial')]
            }
            return res
        return {}

    @api.multi
    def update_ed(self):

        return {
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'poi.purchase.line.update.wizard',
            'type': 'ir.actions.act_window',
            'context': self._context,
            'target': 'new'
        }

    @api.multi
    def _prepare_stock_moves(self, picking):
        res = super(PurchaseOrderLine, self)._prepare_stock_moves(picking)
        for re in res:
            order = self.order_id
            re['colorinterno'] = self.colorinterno.id
            re['colorexterno'] = self.colorinterno.id
            re['edicion'] = self.edicion
            re['price_unit_fob'] = order.company_id.currency_id.compute(
                re['price_unit'], order.currency_id, round=False)
            re['currency_id'] = self.order_id.currency_id.id

        return res
예제 #29
0
class SaleOrder(models.Model):
    _name = 'sale.order'
    _inherit = [_name, 'l10n_br_fiscal.document.mixin']

    @api.model
    def _default_fiscal_operation(self):
        return self.env.user.company_id.sale_fiscal_operation_id

    @api.model
    def _default_copy_note(self):
        return self.env.user.company_id.copy_note

    @api.model
    def _fiscal_operation_domain(self):
        domain = [('state', '=', 'approved')]
        return domain

    fiscal_operation_id = fields.Many2one(
        comodel_name='l10n_br_fiscal.operation',
        readonly=True,
        states={'draft': [('readonly', False)]},
        default=_default_fiscal_operation,
        domain=lambda self: self._fiscal_operation_domain(),
    )

    ind_pres = fields.Selection(
        readonly=True,
        states={'draft': [('readonly', False)]},
    )

    copy_note = fields.Boolean(
        string='Copiar Observação no documentos fiscal',
        default=_default_copy_note,
    )

    cnpj_cpf = fields.Char(
        string='CNPJ/CPF',
        related='partner_id.cnpj_cpf',
    )

    legal_name = fields.Char(
        string='Legal Name',
        related='partner_id.legal_name',
    )

    ie = fields.Char(
        string='State Tax Number/RG',
        related='partner_id.inscr_est',
    )

    discount_rate = fields.Float(
        string='Discount',
        readonly=True,
        states={'draft': [('readonly', False)]},
    )

    amount_gross = fields.Monetary(
        compute='_amount_all',
        string='Amount Gross',
        store=True,
        readonly=True,
        help="Amount without discount.",
    )

    amount_discount = fields.Monetary(
        compute='_amount_all',
        store=True,
        string='Discount (-)',
        readonly=True,
        help="The discount amount.",
    )

    amount_freight = fields.Float(
        compute='_amount_all',
        store=True,
        string='Freight',
        readonly=True,
        default=0.00,
        digits=dp.get_precision('Account'),
        states={'draft': [('readonly', False)]},
    )

    amount_insurance = fields.Float(
        compute='_amount_all',
        store=True,
        string='Insurance',
        readonly=True,
        default=0.00,
        digits=dp.get_precision('Account'),
    )

    amount_costs = fields.Float(
        compute='_amount_all',
        store=True,
        string='Other Costs',
        readonly=True,
        default=0.00,
        digits=dp.get_precision('Account'),
    )

    fiscal_document_count = fields.Integer(
        string='Fiscal Document Count',
        related='invoice_count',
        readonly=True,
    )

    comment_ids = fields.Many2many(
        comodel_name='l10n_br_fiscal.comment',
        relation='sale_order_comment_rel',
        column1='sale_id',
        column2='comment_id',
        string='Comments',
    )

    @api.depends('order_line.price_total')
    def _amount_all(self):
        """Compute the total amounts of the SO."""
        for order in self:
            order.amount_gross = sum(line.price_gross
                                     for line in order.order_line)

            order.amount_discount = sum(line.discount_value
                                        for line in order.order_line)

            order.amount_untaxed = sum(line.price_subtotal
                                       for line in order.order_line)

            order.amount_tax = sum(line.price_tax for line in order.order_line)

            order.amount_total = sum(line.price_total
                                     for line in order.order_line)

            order.amount_freight = sum(line.freight_value
                                       for line in order.order_line)

            order.amount_costs = sum(line.other_costs_value
                                     for line in order.order_line)

            order.amount_insurance = sum(line.insurance_value
                                         for line in order.order_line)

    @api.model
    def fields_view_get(self,
                        view_id=None,
                        view_type="form",
                        toolbar=False,
                        submenu=False):

        order_view = super().fields_view_get(view_id, view_type, toolbar,
                                             submenu)

        if view_type == 'form':
            sub_form_view = order_view.get('fields', {}).get(
                'order_line', {}).get('views', {}).get('form',
                                                       {}).get('arch', {})

            view = self.env['ir.ui.view']

            sub_form_node = etree.fromstring(
                self.env['sale.order.line'].fiscal_form_view(sub_form_view))

            sub_arch, sub_fields = view.postprocess_and_fields(
                'sale.order.line', sub_form_node, None)

            order_view['fields']['order_line']['views']['form'][
                'fields'] = sub_fields

            order_view['fields']['order_line']['views']['form'][
                'arch'] = sub_arch

        return order_view

    @api.onchange('discount_rate')
    def onchange_discount_rate(self):
        for order in self:
            for line in order.order_line:
                line.discount = order.discount_rate
                line._onchange_discount_percent()

    @api.onchange('fiscal_operation_id')
    def _onchange_fiscal_operation_id(self):
        super()._onchange_fiscal_operation_id()
        self.fiscal_position_id = self.fiscal_operation_id.fiscal_position_id

    @api.multi
    def action_view_document(self):
        invoices = self.mapped('invoice_ids')
        action = self.env.ref('l10n_br_fiscal.document_out_action').read()[0]
        if len(invoices) > 1:
            action['domain'] = [
                ('id', 'in', invoices.mapped('fiscal_document_id').ids),
            ]
        elif len(invoices) == 1:
            form_view = [
                (self.env.ref('l10n_br_fiscal.document_form').id, 'form'),
            ]
            if 'views' in action:
                action['views'] = form_view + [
                    (state, view)
                    for state, view in action['views'] if view != 'form'
                ]
            else:
                action['views'] = form_view
            action['res_id'] = invoices.fiscal_document_id.id
        else:
            action = {'type': 'ir.actions.act_window_close'}
        return action

    @api.multi
    def _prepare_invoice(self):
        self.ensure_one()
        result = super()._prepare_invoice()
        result.update(self._prepare_br_fiscal_dict())

        document_type_id = self._context.get('document_type_id')

        if document_type_id:
            document_type = self.env['l10n_br_fiscal.document.type'].browse(
                document_type_id)
        else:
            document_type = self.company_id.document_type_id
            document_type_id = self.company_id.document_type_id.id

        if document_type:
            result['document_type_id'] = document_type_id
            document_serie = document_type.get_document_serie(
                self.company_id, self.fiscal_operation_id)
            if document_serie:
                result['document_serie_id'] = document_serie.id

        if self.fiscal_operation_id:
            if self.fiscal_operation_id.journal_id:
                result['journal_id'] = self.fiscal_operation_id.journal_id.id

        return result

    @api.multi
    def action_invoice_create(self, grouped=False, final=False):

        inv_ids = super().action_invoice_create(grouped=grouped, final=final)

        # In brazilian localization we need to overwrite this method
        # because when there are a sale order line with different Document
        # Fiscal Type the method should be create invoices separated.
        document_type_list = []

        for invoice_id in inv_ids:
            invoice_created_by_super = self.env['account.invoice'].browse(
                invoice_id)

            # Identify how many Document Types exist
            for inv_line in invoice_created_by_super.invoice_line_ids:

                fiscal_document_type = \
                    inv_line.fiscal_operation_line_id.get_document_type(
                        inv_line.invoice_id.company_id)

                if fiscal_document_type.id not in document_type_list:
                    document_type_list.append(fiscal_document_type.id)

            # Check if there more than one Document Type
            if ((fiscal_document_type !=
                 invoice_created_by_super.document_type_id.id)
                    or (len(document_type_list) > 1)):

                # Remove the First Document Type,
                # already has Invoice created
                invoice_created_by_super.document_type_id =\
                    document_type_list.pop(0)

                for document_type in document_type_list:
                    document_type = self.env[
                        'l10n_br_fiscal.document.type'].browse(document_type)

                    inv_obj = self.env['account.invoice']
                    invoices = {}
                    references = {}
                    invoices_origin = {}
                    invoices_name = {}

                    for order in self:
                        group_key = order.id if grouped else (
                            order.partner_invoice_id.id, order.currency_id.id)

                        if group_key not in invoices:
                            inv_data = order.with_context(
                                document_type_id=document_type.id
                            )._prepare_invoice()
                            invoice = inv_obj.create(inv_data)
                            references[invoice] = order
                            invoices[group_key] = invoice
                            invoices_origin[group_key] = [invoice.origin]
                            invoices_name[group_key] = [invoice.name]
                            inv_ids.append(invoice.id)
                        elif group_key in invoices:
                            if order.name not in invoices_origin[group_key]:
                                invoices_origin[group_key].append(order.name)
                            if (order.client_order_ref
                                    and order.client_order_ref
                                    not in invoices_name[group_key]):
                                invoices_name[group_key].append(
                                    order.client_order_ref)

                    # Update Invoice Line
                    for inv_line in invoice_created_by_super.invoice_line_ids:
                        fiscal_document_type = \
                            inv_line.fiscal_operation_line_id.get_document_type(
                                inv_line.invoice_id.company_id)
                        if fiscal_document_type.id == document_type.id:
                            inv_line.invoice_id = invoice.id

        return inv_ids
예제 #30
0
class SellDelivery(models.Model):
    _name = 'sell.delivery'
    _inherits = {'wh.move': 'sell_move_id'}
    _inherit = ['mail.thread']
    _description = u'销售发货单'
    _order = 'date desc, id desc'

    @api.one
    @api.depends('line_out_ids.subtotal', 'discount_amount', 'partner_cost',
                 'receipt', 'partner_id', 'line_in_ids.subtotal')
    def _compute_all_amount(self):
        '''当优惠金额改变时,改变优惠后金额、本次欠款和总欠款'''
        total = 0
        if self.line_out_ids:
            # 发货时优惠前总金
            total = sum(line.subtotal for line in self.line_out_ids)
        elif self.line_in_ids:
            # 退货时优惠前总金额
            total = sum(line.subtotal for line in self.line_in_ids)
        self.amount = total - self.discount_amount
        self.debt = self.amount - self.receipt + self.partner_cost
        # 本次欠款变化时,总欠款应该变化
        self.total_debt = self.partner_id.receivable + self.debt

    @api.one
    @api.depends('is_return', 'invoice_id.reconciled', 'invoice_id.amount')
    def _get_sell_money_state(self):
        '''返回收款状态'''
        if not self.is_return:
            if self.invoice_id.reconciled == 0:
                self.money_state = u'未收款'
            elif self.invoice_id.reconciled < self.invoice_id.amount:
                self.money_state = u'部分收款'
            elif self.invoice_id.reconciled == self.invoice_id.amount:
                self.money_state = u'全部收款'
        # 返回退款状态
        if self.is_return:
            if self.invoice_id.reconciled == 0:
                self.return_state = u'未退款'
            elif abs(self.invoice_id.reconciled) < abs(self.invoice_id.amount):
                self.return_state = u'部分退款'
            elif self.invoice_id.reconciled == self.invoice_id.amount:
                self.return_state = u'全部退款'

    currency_id = fields.Many2one('res.currency', u'外币币别', readonly=True,
                                  help=u'外币币别')
    sell_move_id = fields.Many2one('wh.move', u'发货单', required=True,
                                   ondelete='cascade',
                                   help=u'发货单号')
    is_return = fields.Boolean(u'是否退货', default=lambda self:
                               self.env.context.get('is_return'),
                               help=u'是否为退货类型')
    order_id = fields.Many2one('sell.order', u'订单号', copy=False,
                               ondelete='cascade',
                               help=u'产生发货单/退货单的销货订单')
    invoice_id = fields.Many2one('money.invoice', u'发票号',
                                 copy=False, ondelete='set null',
                                 help=u'产生的发票号')
    date_due = fields.Date(u'到期日期', copy=False,
                           default=lambda self: fields.Date.context_today(
                               self),
                           help=u'收款截止日期')
    discount_rate = fields.Float(u'优惠率(%)', states=READONLY_STATES,
                                 help=u'整单优惠率')
    discount_amount = fields.Float(u'优惠金额', states=READONLY_STATES,
                                   digits=dp.get_precision('Amount'),
                                   help=u'整单优惠金额,可由优惠率自动计算得出,也可手动输入')
    amount = fields.Float(u'优惠后金额', compute=_compute_all_amount,
                          store=True, readonly=True,
                          digits=dp.get_precision('Amount'),
                          help=u'总金额减去优惠金额')
    partner_cost = fields.Float(u'客户承担费用',
                                digits=dp.get_precision('Amount'),
                                help=u'客户承担费用')
    receipt = fields.Float(u'本次收款', states=READONLY_STATES,
                           digits=dp.get_precision('Amount'),
                           help=u'本次收款金额')
    bank_account_id = fields.Many2one('bank.account',
                                      u'结算账户', ondelete='restrict',
                                      help=u'用来核算和监督企业与其他单位或个人之间的债权债务的结算情况')
    debt = fields.Float(u'本次欠款', compute=_compute_all_amount,
                        store=True, readonly=True, copy=False,
                        digits=dp.get_precision('Amount'),
                        help=u'本次欠款金额')
    total_debt = fields.Float(u'总欠款', compute=_compute_all_amount,
                              store=True, readonly=True, copy=False,
                              digits=dp.get_precision('Amount'),
                              help=u'该客户的总欠款金额')
    cost_line_ids = fields.One2many('cost.line', 'sell_id', u'销售费用',
                                    copy=False,
                                    help=u'销售费用明细行')
    money_state = fields.Char(u'收款状态', compute=_get_sell_money_state,
                              store=True, default=u'未收款',
                              help=u"销售发货单的收款状态", index=True, copy=False)
    return_state = fields.Char(u'退款状态', compute=_get_sell_money_state,
                               store=True, default=u'未退款',
                               help=u"销售退货单的退款状态", index=True, copy=False)
    contact = fields.Char(u'联系人', states=READONLY_STATES,
                          help=u'客户方的联系人')
    address_id = fields.Many2one('partner.address', u'联系人地址', states=READONLY_STATES,
                                 help=u'联系地址')
    mobile = fields.Char(u'手机', states=READONLY_STATES,
                         help=u'联系手机')
    modifying = fields.Boolean(u'差错修改中', default=False,
                               help=u'是否处于差错修改中')
    origin_id = fields.Many2one('sell.delivery', u'来源单据')
    voucher_id = fields.Many2one('voucher', u'出库凭证', readonly=True,
                                 help=u'审核时产生的出库凭证')

    @api.onchange('address_id')
    def onchange_address_id(self):
        ''' 选择地址填充 联系人、电话 '''
        if self.address_id:
            self.contact = self.address_id.contact
            self.mobile = self.address_id.mobile

    @api.onchange('partner_id')
    def onchange_partner_id(self):
        '''选择客户带出其默认地址信息'''
        if self.partner_id:
            self.contact = self.partner_id.contact
            self.mobile = self.partner_id.mobile

            for child in self.partner_id.child_ids:
                if child.is_default_add:
                    self.address_id = child.id
            if self.partner_id.child_ids and not any([child.is_default_add for child in self.partner_id.child_ids]):
                partners_add = self.env['partner.address'].search(
                    [('partner_id', '=', self.partner_id.id)], order='id')
                self.address_id = partners_add[0].id

            for line in self.line_out_ids:
                if line.goods_id.tax_rate and self.partner_id.tax_rate:
                    if line.goods_id.tax_rate >= self.partner_id.tax_rate:
                        line.tax_rate = self.partner_id.tax_rate
                    else:
                        line.tax_rate = line.goods_id.tax_rate
                elif line.goods_id.tax_rate and not self.partner_id.tax_rate:
                    line.tax_rate = line.goods_id.tax_rate
                elif not line.goods_id.tax_rate and self.partner_id.tax_rate:
                    line.tax_rate = self.partner_id.tax_rate
                else:
                    line.tax_rate = self.env.user.company_id.output_tax_rate

            address_list = [
                child_list.id for child_list in self.partner_id.child_ids]
            if address_list:
                return {'domain': {'address_id': [('id', 'in', address_list)]}}
            else:
                self.address_id = False

    @api.onchange('discount_rate', 'line_in_ids', 'line_out_ids')
    def onchange_discount_rate(self):
        '''当优惠率或订单行发生变化时,单据优惠金额发生变化'''
        total = 0
        if self.line_out_ids:
            # 发货时优惠前总金额
            total = sum(line.subtotal for line in self.line_out_ids)
        elif self.line_in_ids:
            # 退货时优惠前总金额
            total = sum(line.subtotal for line in self.line_in_ids)
        if self.discount_rate:
            self.discount_amount = total * self.discount_rate * 0.01

    def get_move_origin(self, vals):
        return self._name + (self.env.context.get('is_return') and '.return'
                             or '.sell')

    @api.model
    def create(self, vals):
        '''创建销售发货单时生成有序编号'''
        if not self.env.context.get('is_return'):
            name = self._name
        else:
            name = 'sell.return'
        if vals.get('name', '/') == '/':
            vals['name'] = self.env['ir.sequence'].next_by_code(name) or '/'

        vals.update({
            'origin': self.get_move_origin(vals),
            'finance_category_id': self.env.ref('finance.categ_sell_goods').id,
        })

        return super(SellDelivery, self).create(vals)

    @api.multi
    def unlink(self):
        for delivery in self:
            if delivery.state == 'done':
                raise UserError(u'不能删除已审核的销售发货单')

            delivery.sell_move_id.unlink()

    def goods_inventory(self, vals):
        """
        审核时若仓库中商品不足,则产生补货向导生成其他入库单并审核。
        :param vals: 创建其他入库单需要的字段及取值信息构成的字典
        :return:
        """
        auto_in = self.env['wh.in'].create(vals)
        line_ids = [line.id for line in auto_in.line_in_ids]
        self.with_context({'wh_in_line_ids': line_ids}).sell_delivery_done()
        return True

    @api.one
    def _wrong_delivery_done(self):
        '''审核时不合法的给出报错'''
        if self.state == 'done':
            raise UserError(u'请不要重复审核!')
        for line in self.line_in_ids:
            if line.goods_qty <= 0 or line.price_taxed < 0:
                raise UserError(u'商品 %s 的数量和商品含税单价不能小于0!' % line.goods_id.name)
        if not self.bank_account_id and self.receipt:
            raise UserError(u'收款额不为空时,请选择结算账户!')
        decimal_amount = self.env.ref('core.decimal_amount')
        if float_compare(self.receipt, self.amount + self.partner_cost, precision_digits=decimal_amount.digits) == 1:
            raise UserError(u'本次收款金额不能大于优惠后金额!\n本次收款金额:%s 优惠后金额:%s' %
                            (self.receipt, self.amount + self.partner_cost))
        # 发库单/退货单 计算客户的 本次发货金额+客户应收余额 是否小于客户信用额度, 否则报错
        if not self.is_return:
            amount = self.amount + self.partner_cost
            if self.partner_id.credit_limit != 0:
                if float_compare(amount - self.receipt + self.partner_id.receivable, self.partner_id.credit_limit,
                                 precision_digits=decimal_amount.digits) == 1:
                    raise UserError(u'本次发货金额 + 客户应收余额 - 本次收款金额 不能大于客户信用额度!\n\
                     本次发货金额:%s\n 客户应收余额:%s\n 本次收款金额:%s\n客户信用额度:%s' % (
                        amount, self.receipt, self.partner_id.receivable, self.partner_id.credit_limit))

    def _line_qty_write(self):
        if self.order_id:
            for line in self.line_in_ids:
                line.sell_line_id.quantity_out -= line.goods_qty
            for line in self.line_out_ids:
                line.sell_line_id.quantity_out += line.goods_qty

        return

    def _get_invoice_vals(self, partner_id, category_id, date, amount, tax_amount):
        '''返回创建 money_invoice 时所需数据'''
        return {
            'move_id': self.sell_move_id.id,
            'name': self.name,
            'partner_id': partner_id.id,
            'category_id': category_id.id,
            'date': date,
            'amount': amount,
            'reconciled': 0,
            'to_reconcile': amount,
            'tax_amount': tax_amount,
            'date_due': self.date_due,
            'state': 'draft',
            'currency_id': self.currency_id.id,
            'note': self.note,
        }

    def _delivery_make_invoice(self):
        '''发货单/退货单 生成结算单'''
        if not self.is_return:
            amount = self.amount + self.partner_cost
            tax_amount = sum(line.tax_amount for line in self.line_out_ids)
        else:
            amount = -(self.amount + self.partner_cost)
            tax_amount = - sum(line.tax_amount for line in self.line_in_ids)
        category = self.env.ref('money.core_category_sale')
        invoice_id = False
        if not float_is_zero(amount, 2):
            invoice_id = self.env['money.invoice'].create(
                self._get_invoice_vals(
                    self.partner_id, category, self.date, amount, tax_amount)
            )
        return invoice_id

    def _sell_amount_to_invoice(self):
        '''销售费用产生结算单'''
        invoice_id = False
        if sum(cost_line.amount for cost_line in self.cost_line_ids) > 0:
            for line in self.cost_line_ids:
                if not float_is_zero(line.amount, 2):
                    invoice_id = self.env['money.invoice'].create(
                        self._get_invoice_vals(
                            line.partner_id, line.category_id, self.date, line.amount + line.tax, line.tax)
                    )
        return invoice_id

    def _make_money_order(self, invoice_id, amount, this_reconcile):
        '''生成收款单'''
        categ = self.env.ref('money.core_category_sale')
        money_lines = [{
            'bank_id': self.bank_account_id.id,
            'amount': this_reconcile,
        }]
        source_lines = [{
            'name': invoice_id and invoice_id.id,
            'category_id': categ.id,
            'date': invoice_id and invoice_id.date,
            'amount': amount,
            'reconciled': 0.0,
            'to_reconcile': amount,
            'this_reconcile': this_reconcile,
        }]
        rec = self.with_context(type='get')
        money_order = rec.env['money.order'].create({
            'partner_id': self.partner_id.id,
            'date': self.date,
            'line_ids': [(0, 0, line) for line in money_lines],
            'source_ids': [(0, 0, line) for line in source_lines],
            'amount': amount,
            'reconciled': this_reconcile,
            'to_reconcile': amount,
            'state': 'draft',
            'origin_name': self.name,
            'note': self.note,
            'sell_id': self.order_id.id,
        })
        return money_order

    def _create_voucher_line(self, account_id, debit, credit, voucher, goods_id, goods_qty):
        """
        创建凭证明细行
        :param account_id: 科目
        :param debit: 借方
        :param credit: 贷方
        :param voucher: 凭证
        :param goods_id: 商品
        :return:
        """
        voucher_line = self.env['voucher.line'].create({
            'name': u'%s %s' % (self.name, self.note or ''),
            'account_id': account_id and account_id.id,
            'debit': debit,
            'credit': credit,
            'voucher_id': voucher and voucher.id,
            'goods_qty': goods_qty,
            'goods_id': goods_id and goods_id.id,
        })
        return voucher_line

    @api.multi
    def create_voucher(self):
        '''
        销售发货单、退货单审核时生成会计凭证
        借:主营业务成本(核算分类上会计科目)
        贷:库存商品(商品分类上会计科目)

        当一张发货单有多个商品的时候,按对应科目汇总生成多个贷方凭证行。

        退货单生成的金额为负
        '''
        self.ensure_one()
        voucher = self.env['voucher'].create({'date': self.date})

        sum_amount = 0
        line_ids = self.is_return and self.line_in_ids or self.line_out_ids
        for line in line_ids:   # 发货单/退货单明细
            cost = self.is_return and -line.cost or line.cost

            if cost:  # 贷方明细
                sum_amount += cost
                self._create_voucher_line(line.goods_id.category_id.account_id,
                                          0, cost, voucher, line.goods_id, line.goods_qty)
            else:
                # 缺货审核发货单时不产生出库凭证
                continue
        if sum_amount:  # 借方明细
            self._create_voucher_line(self.sell_move_id.finance_category_id.account_id,
                                      sum_amount, 0, voucher, False, 0)

        if len(voucher.line_ids) > 0:
            voucher.voucher_done()
            return voucher
        else:
            voucher.unlink()

    @api.one
    def auto_reconcile_sell_order(self):
        ''' 预收款与结算单自动核销 '''
        all_delivery_amount = 0
        for delivery in self.order_id.delivery_ids:
            all_delivery_amount += delivery.amount

        if self.order_id.received_amount and self.order_id.received_amount == all_delivery_amount:
            adv_pay_result = []
            receive_source_result = []
            # 预收款
            adv_pay_orders = self.env['money.order'].search([('partner_id', '=', self.partner_id.id),
                                                             ('type', '=', 'get'),
                                                             ('state', '=', 'done'),
                                                             ('to_reconcile',
                                                              '!=', 0),
                                                             ('sell_id', '=', self.order_id.id)])
            for order in adv_pay_orders:
                adv_pay_result.append((0, 0, {'name': order.id,
                                              'amount': order.amount,
                                              'date': order.date,
                                              'reconciled': order.reconciled,
                                              'to_reconcile': order.to_reconcile,
                                              'this_reconcile': order.to_reconcile,
                                              }))
            # 结算单
            receive_source_name = [
                delivery.name for delivery in self.order_id.delivery_ids]
            receive_source_orders = self.env['money.invoice'].search([('category_id.type', '=', 'income'),
                                                                      ('partner_id', '=',
                                                                       self.partner_id.id),
                                                                      ('to_reconcile',
                                                                       '!=', 0),
                                                                      ('name', 'in', receive_source_name)])
            for invoice in receive_source_orders:
                receive_source_result.append((0, 0, {
                    'name': invoice.id,
                    'category_id': invoice.category_id.id,
                    'amount': invoice.amount,
                    'date': invoice.date,
                    'reconciled': invoice.reconciled,
                    'to_reconcile': invoice.to_reconcile,
                    'date_due': invoice.date_due,
                    'this_reconcile': invoice.to_reconcile,
                }))
            # 创建核销单
            reconcile_order = self.env['reconcile.order'].create({
                'partner_id': self.partner_id.id,
                'business_type': 'adv_pay_to_get',
                'advance_payment_ids': adv_pay_result,
                'receivable_source_ids': receive_source_result,
                'note': u'自动核销',
            })
            reconcile_order.reconcile_order_done()  # 自动审核

    @api.multi
    def sell_delivery_done(self):
        '''审核销售发货单/退货单,更新本单的收款状态/退款状态,并生成结算单和收款单'''
        for record in self:
            record._wrong_delivery_done()
            # 库存不足 生成零的
            if self.env.user.company_id.is_enable_negative_stock:
                result_vals = self.env['wh.move'].create_zero_wh_in(
                    record, record._name)
                if result_vals:
                    return result_vals
            # 调用wh.move中审核方法,更新审核人和审核状态
            record.sell_move_id.approve_order()
            # 将发货/退货数量写入销货订单行
            if record.order_id:
                record._line_qty_write()
            voucher = False
            # 创建出库的会计凭证,生成盘盈的入库单的不产生出库凭证
            if not self.env.user.company_id.endmonth_generation_cost:
                voucher = record.create_voucher()
            # 发货单/退货单 生成结算单
            invoice_id = record._delivery_make_invoice()
            record.write({
                'voucher_id': voucher and voucher.id,
                'invoice_id': invoice_id and invoice_id.id,
                'state': 'done',  # 为保证审批流程顺畅,否则,未审批就可审核
            })
            # 销售费用产生结算单
            record._sell_amount_to_invoice()
            # 生成收款单,并审核
            if record.receipt:
                flag = not record.is_return and 1 or -1
                amount = flag * (record.amount + record.partner_cost)
                this_reconcile = flag * record.receipt
                money_order = record._make_money_order(
                    invoice_id, amount, this_reconcile)
                money_order.money_order_done()

            # 先收款后发货订单自动核销
            self.auto_reconcile_sell_order()

            # 生成分拆单 FIXME:无法跳转到新生成的分单
            if record.order_id and not record.modifying:
                return record.order_id.sell_generate_delivery()

    @api.one
    def sell_delivery_draft(self):
        '''反审核销售发货单/退货单,更新本单的收款状态/退款状态,并删除生成的结算单、收款单及凭证'''
        # 查找产生的收款单
        source_line = self.env['source.order.line'].search(
            [('name', '=', self.invoice_id.id)])
        for line in source_line:
            line.money_id.money_order_draft()
            line.money_id.unlink()
            # FIXME:查找产生的核销单,反审核后删掉
        # 查找产生的结算单
        invoice_ids = self.env['money.invoice'].search(
            [('name', '=', self.invoice_id.name)])
        # 不能反审核已核销的发货单
        for invoice in invoice_ids:
            if invoice.to_reconcile == 0 and invoice.reconciled == invoice.amount:
                raise UserError(u'发货单已核销,不能反审核!')
        invoice_ids.money_invoice_draft()
        invoice_ids.unlink()
        # 如果存在分单,则将差错修改中置为 True,再次审核时不生成分单
        self.write({
            'modifying': False,
            'state': 'draft',
        })
        delivery_ids = self.search(
            [('order_id', '=', self.order_id.id)])
        if len(delivery_ids) > 1:
            self.write({
                'modifying': True,
                'state': 'draft',
            })
        # 将原始订单中已执行数量清零
        if self.order_id:
            line_ids = not self.is_return and self.line_in_ids or self.line_out_ids
            for line in line_ids:
                line.sell_line_id.quantity_out -= line.goods_qty
        # 调用wh.move中反审核方法,更新审核人和审核状态
        self.sell_move_id.cancel_approved_order()

        # 删除产生的出库凭证
        voucher, self.voucher_id = self.voucher_id, False
        if voucher and voucher.state == 'done':
            voucher.voucher_draft()
        voucher.unlink()

    @api.multi
    def sell_to_return(self):
        '''销售发货单转化为销售退货单'''
        return_goods = {}

        return_order_draft = self.search([
            ('is_return', '=', True),
            ('origin_id', '=', self.id),
            ('state', '=', 'draft')
        ])
        if return_order_draft:
            raise UserError(u'销售发货单存在草稿状态的退货单!')

        return_order = self.search([
            ('is_return', '=', True),
            ('origin_id', '=', self.id),
            ('state', '=', 'done')
        ])
        for order in return_order:
            for return_line in order.line_in_ids:
                # 用产品、属性、批次做key记录已退货数量
                t_key = (return_line.goods_id.id,
                         return_line.attribute_id.id, return_line.lot)
                if return_goods.get(t_key):
                    return_goods[t_key] += return_line.goods_qty
                else:
                    return_goods[t_key] = return_line.goods_qty
        receipt_line = []
        for line in self.line_out_ids:
            qty = line.goods_qty
            l_key = (line.goods_id.id, line.attribute_id.id, line.lot_id.lot)
            if return_goods.get(l_key):
                qty = qty - return_goods[l_key]
            if qty > 0:
                dic = {
                    'goods_id': line.goods_id.id,
                    'attribute_id': line.attribute_id.id,
                    'uom_id': line.uom_id.id,
                    'warehouse_id': line.warehouse_dest_id.id,
                    'warehouse_dest_id': line.warehouse_id.id,
                    'goods_qty': qty,
                    'sell_line_id': line.sell_line_id.id,
                    'price_taxed': line.price_taxed,
                    'discount_rate': line.discount_rate,
                    'discount_amount': line.discount_amount,
                    'type': 'in',
                }
                if line.goods_id.using_batch:
                    dic.update({'lot': line.lot_id.lot})
                receipt_line.append(dic)
        if len(receipt_line) == 0:
            raise UserError(u'该订单已全部退货!')
        vals = {'partner_id': self.partner_id.id,
                'is_return': True,
                'order_id': self.order_id.id,
                'origin_id': self.id,
                'origin': 'sell.delivery.return',
                'warehouse_dest_id': self.warehouse_id.id,
                'warehouse_id': self.warehouse_dest_id.id,
                'bank_account_id': self.bank_account_id.id,
                'date_due': (datetime.datetime.now()).strftime(ISODATEFORMAT),
                'date': (datetime.datetime.now()).strftime(ISODATEFORMAT),
                'line_in_ids': [(0, 0, line) for line in receipt_line],
                'discount_amount': self.discount_amount,
                }
        delivery_return = self.with_context(is_return=True).create(vals)
        view_id = self.env.ref('sell.sell_return_form').id
        name = u'销货退货单'
        return {
            'name': name,
            'view_type': 'form',
            'view_mode': 'form',
            'view_id': False,
            'views': [(view_id, 'form')],
            'res_model': 'sell.delivery',
            'type': 'ir.actions.act_window',
            'res_id': delivery_return.id,
            'target': 'current'
        }
# Copyright 2016-17 Eficent Business and IT Consulting Services S.L.
#   (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from odoo import api, fields, models
from datetime import datetime
from odoo.addons import decimal_precision as dp
from odoo.tools import float_compare, float_round


UNIT = dp.get_precision('Product Unit of Measure')


class StockWarehouseOrderpoint(models.Model):
    _inherit = 'stock.warehouse.orderpoint'

    procure_recommended_qty = fields.Float(
        string='Procure Recommendation',
        compute="_compute_procure_recommended",
        digits=UNIT,
    )
    procure_recommended_date = fields.Date(
        string='Recommended Request Date',
        compute="_compute_procure_recommended",
    )

    @api.multi
    def _get_procure_recommended_qty(self, virtual_qty, op_qtys):
        self.ensure_one()
        procure_recommended_qty = 0.0
        qty = max(self.product_min_qty, self.product_max_qty) - virtual_qty
예제 #32
0
class AccountInvoice(models.Model):
    _inherit = 'account.invoice'

    @api.model
    def nuevos_datos(self):

        # Lanzar asistente de Nota de crédito
        context = {}
        context['active_ids'] = self._ids
        context['invoice_id'] = len(self._ids) > 0 and self._ids[0] or False

        view_id = self.env.ref('poi_bol_base.wiz_invoice_refund')

        wizard_form = {
            'name': u"Modificar Factura Original",
            'view_mode': 'form',
            'view_type': 'form',
            'view_id': view_id,
            'res_model': 'wiz.invoice_refund',
            'type': 'ir.actions.act_window',
            'search_view_id': False,
            'target': 'new',
            'context': context,
        }
        return wizard_form

    def _get_tipo_sin(self):
        # Asignar Tipo de Factura según la numeración de impuestos internos
        tipo = self.env.context.get('tipo_fac', False)
        if not tipo:
            inv_type = self.env.context.get('type', self.type or '')
            tipo = type_sin_dict[inv_type]
        return tipo

    def _get_tipo_com(self):
        # Asignar Tipo '1' sólo a Facturas de Compra
        tipo = False
        if self.env.context.get('type', '') == 'in_invoice':
            tipo = '1'
        return tipo

    @api.one
    @api.depends('estado_fac')
    def _get_estado_fac(self):
        self.estado_fac_display = self.estado_fac

    def _get_default_dosif(self):
        dosif_users_pool = self.env['poi_bol_base.cc_dosif.users']
        dosif_users_ids = dosif_users_pool.search([
            ('user_id', '=', self.env.uid), ('user_default', '=', True)
        ])
        if dosif_users_ids:
            for dosif_users in dosif_users_ids:
                if dosif_users.dosif_id.activa and dosif_users.dosif_id.applies == 'out_invoice':
                    return dosif_users.dosif_id.id

    @api.one
    @api.depends('invoice_line_ids')
    def _suma_desc(self):
        for inv in self:
            desc = 0.0
            for line in inv.invoice_line_ids:
                desc += line.price_unit * (
                    (line.discount or 0.0) / 100.0) * line.quantity
            self.sum_desc = desc

    @api.one
    @api.depends('invoice_line_ids', 'tax_line_ids')
    def _amount_all_bs(self):
        for invoice in self:
            amount_untaxed = 0.0
            amount_tax = 0.0
            for line in invoice.invoice_line_ids:
                amount_untaxed += line.price_subtotal
            for line in invoice.tax_line_ids:
                amount_tax += line.amount
            amount_total = amount_tax + amount_untaxed
            cur = invoice.currency_id
            if cur.name == 'BOB':
                self.total_bs = amount_total
                self.tax_bs = amount_tax
            else:
                cur_bob = self.env['res.currency'].search(
                    [('name', '=', 'BOB')], limit=1)
                if cur_bob:
                    cur_bob = cur_bob[0]
                    date_rate = invoice.date_invoice and invoice.date_invoice[
                        0:10] or False
                    self.total_bs = cur.with_context(date=date_rate).compute(
                        amount_total, cur_bob)
                    self.tax_bs = cur.with_context(date=date_rate).compute(
                        amount_tax, cur_bob)

    @api.one
    @api.depends('invoice_line_ids.price_subtotal', 'tax_line_ids.amount',
                 'currency_id', 'company_id', 'date_invoice', 'type')
    def _compute_amount(self):
        super(AccountInvoice, self)._compute_amount()

        # Calculo preliminar totales segun tipo especificos de impuestos SIN
        if self.tipo_fac != '12':
            self.ice = sum(line.amount for line in self.tax_line_ids
                           if line.type_bol == 'ice')
            self.iva = sum(line.amount for line in self.tax_line_ids
                           if line.type_bol == 'iva')
            self.exe = sum(line.amount for line in self.tax_line_ids
                           if line.type_bol == 'exe')

    @api.one
    @api.depends('invoice_line_ids')
    def _con_impuesto(self):
        for inv in self:
            self.con_imp = False
            for line in inv.invoice_line_ids:
                for tax in line.invoice_line_tax_ids:
                    if tax.apply_lcv:
                        self.con_imp = True

    @api.model
    def _con_impuesto_search(self, operator, operand):
        if not operator:
            return []

        if operator == '=':
            if operand:
                self.env.cr.execute(
                    """select il.invoice_id from account_invoice_line il inner join account_invoice_line_tax ilt on il.id = ilt.invoice_line_id inner join account_tax tx on ilt.tax_id = tx.id where tx.apply_lcv = True group by il.invoice_id  """
                )
            else:
                self.env.cr.execute(
                    """select il.id from account_invoice_line il where il.id not in (select ilt.invoice_line_id from account_invoice_line_tax ilt inner join account_tax tx on ilt.tax_id = tx.id where tx.amount > 0 group by ilt.invoice_line_id) group by il.id"""
                )

        res = self.env.cr.fetchall()
        if not res:
            return [('id', '=', 0)]
        return [('id', 'in', [x[0] for x in res])]

    picking_id = fields.Many2one('stock.picking', 'Items Picking')
    refunds_id = fields.Many2one('account.invoice',
                                 'Factura reintegrada',
                                 copy=False)
    note_from_id = fields.Many2one(
        'account.invoice',
        'Factura origen de Nota',
        copy=False,
        help=u"Factura de la cual originó esta Nota de Credito")
    nit = fields.Char('NIT', size=12, help="NIT o CI del cliente.")
    razon = fields.Char(u'Razón Social',
                        help=u"Nombre o Razón Social para la Factura.")
    cc_date = fields.Date(
        'Fecha factura fiscal',
        help=
        u"Fecha de factura fiscal de Compra según copia física. Usar en caso de requerir especificar una fecha de factura diferente a la fecha de contabilización. En caso de dejar este campo vacío, se usara la fecha de Factura de la cabecera."
    )
    cc_nro = fields.Integer('Nro. Factura',
                            help=u"Número de factura fiscal.",
                            copy=False)
    cc_aut = fields.Char(u'Nro. Autorización', help=u"Número de autorización.")
    # cc_aut debería ser .integer_big(). Pero openerp no parsea el campo en vista. Resuelto en fución _init_
    cc_dos = fields.Many2one(
        'poi_bol_base.cc_dosif',
        string='Serie dosificación',
        default=_get_default_dosif,
        readonly=True,
        states={'draft': [('readonly', False)]},
        domain="[('applies', '=', type)]",
        help=
        u"Serie de dosificación según parametrización. Asocia Número de autorizacción y Llave de dosificación."
    )
    cc_dos_autonum = fields.Boolean(related='cc_dos.auto_num',
                                    string='Auto numera')
    cc_cod = fields.Char(
        u'Código de control',
        size=14,
        help=
        u"Codigo de representación única para el SIN. Introducir manualmente para Compras."
    )
    total_bs = fields.Float(compute='_amount_all_bs',
                            string='Total (Bs.)',
                            store=True)
    tax_bs = fields.Float(compute='_amount_all_bs',
                          string='Impuesto (Bs.)',
                          store=True)
    sum_desc = fields.Float(
        compute="_suma_desc",
        method=True,
        string="Descuentos obtenidos",
        store=True,
        digits=dp.get_precision('Account'),
        help=
        u"Descuentos, Bonificaciones y Rebajas obtenidas. Es el descuento impositivo de factura que se hace visible en el Libro de Compras."
    )
    contract_nr = fields.Char(
        u'N° de contrato',
        help=u"El número de contrato registrado para fines de Bancarización.")
    # Para Libro CV:
    con_imp = fields.Boolean(
        compute='_con_impuesto',
        method=True,
        string='Incluye Impuesto',
        search='_con_impuesto_search',
        help=
        u"Indica la facturas que incluyen impuesto para LCV. Verdadero si tan sólo una línea de la Factura tiene impuesto LCV"
    )
    tipo_fac = fields.Selection(
        [('1', 'Compra'), ('2', 'Boleto BCP'), ('3', 'Importación'),
         ('4', 'Recibo de Alquiler'), ('5', 'Nota de débito proveedor'),
         ('6', 'Nota de crédito cliente'), ('7', 'Venta'),
         ('8', 'Nota de débito cliente'), ('9', 'Nota de crédito proveedor'),
         ('10', 'Sin Asignar'), ('11', 'Rectificación'), ('12', 'Dui'),
         ('13', u'Exportación')],
        string='Tipo de Factura',
        default=_get_tipo_sin,
        help=u"Tipificación de facturas para fines técnicos.")
    estado_fac = fields.Selection([('V', u'Válida'), ('A', 'Anulada'),
                                   ('E', 'Extraviada'), ('N', 'No Utilizada'),
                                   ('na', 'No Aplica'),
                                   ('C', 'Emitida en Contingencia')],
                                  'Estado SIN',
                                  default='V',
                                  copy=False)
    estado_fac_display = fields.Selection([('V', u'Válida'), ('A', 'Anulada'),
                                           ('E', 'Extraviada'),
                                           ('N', 'No Utilizada'),
                                           ('na', 'No Aplica')],
                                          string='Estado SIN Display',
                                          readonly=True,
                                          compute='_get_estado_fac')
    iva = fields.Float('Importe IVA',
                       digits=dp.get_precision('Account'),
                       states={
                           'open': [('readonly', True)],
                           'paid': [('readonly', True)]
                       })
    ice = fields.Float('Importe ICE', digits=dp.get_precision('Account'))
    exento = fields.Float(
        'Importe Exentos',
        digits=dp.get_precision('Account'),
        help=u"Importe Exentos o Ventas gravadas a tasa cero.")
    exporta = fields.Float('Exportaciones',
                           digits=dp.get_precision('Account'),
                           states={
                               'open': [('readonly', True)],
                               'paid': [('readonly', True)]
                           },
                           help="Exportaciones y operaciones Exentas")
    supplier_invoice_number = fields.Char(
        string='Ref. documento Proveedor',
        help=
        "La codificación referencial provista por el Proveedor (No correspode al número de factura impositivo).",
        readonly=True,
        states={'draft': [('readonly', False)]})
    # Para Importaciones. Incluidos ya en poi_bol_base porque el Libro de Compras depende de ellos
    imp_pol = fields.Char(
        u'Nro. Póliza Importación',
        size=16,
        help=
        u"Número de póliza de importación para Libro de compras. Formato AAAADDDCNNNNNNNN, donde: AAAA=Año, DDD=Código de la Aduana, C=Tipo de Trámite, NNNNNNNN=Número Correlativo"
    )
    iva_pol = fields.Float(
        u'IVA Póliza',
        digits=dp.get_precision('Account'),
        help=
        u"Crédito fiscal IVA según Póliza de Importación. Este valor sera usado como base de cálculo en el Libro de Compras para esta transacción!"
    )
    tipo_com = fields.Selection(
        [('1', 'Mercado Interno'), ('2', 'Mercado Interno NO gravadas'),
         ('3', 'Sujetas a proporcionalidad'), ('4', 'Destino Exportaciones'),
         ('5', 'Interno y Exportaciones')],
        string='Tipo de Compra',
        default=_get_tipo_com,
        help=
        u"""Valor único del 1 al 5 que representa el destino que se le dará a la compra realizada:
                                            1 = Compras para mercado interno con destino a actividades gravadas,
                                            2 = Compras para mercado interno con destino a actividades no gravadas,
                                            3 = Compras sujetas a proporcionalidad,
                                            4 = Compras para exportaciones,
                                            5 = Compras tanto para el mercado interno como para exportaciones."""
    )

    _sql_constraints = [
        ('check_nit', "CHECK (nit ~ '^[0-9\.]+$')",
         u'NIT sólo acepta valores numéricos y que no empiecen con cero!'),
        ('check_cc_aut', "CHECK (cc_aut ~ '^[0-9\.]+$')",
         u'Nro Autorización sólo acepta valores numéricos!'),
        ('check_cc_cod',
         "CHECK (cc_cod='' OR cc_cod ~ '[0-9A-F][0-9A-F][-][0-9A-F][0-9A-F][-][0-9A-F][0-9A-F][-][0-9A-F][0-9A-F]')",
         u'Formato de Codigo de control no valido! Debe tener la forma XX-XX-XX-XX (valores permitidos: 0-9 y A-F)'
         ),
    ]

    @api.onchange('partner_id', 'company_id')
    def _onchange_partner_id(self):
        super(AccountInvoice, self)._onchange_partner_id()
        if self.partner_id:
            self.nit = (self.partner_id.commercial_partner_id.nit != 0
                        and self.partner_id.commercial_partner_id.nit) or (
                            self.partner_id.commercial_partner_id.ci != 0
                            and self.partner_id.commercial_partner_id.ci) or ''
            self.razon = self.partner_id.commercial_partner_id.razon_invoice or self.partner_id.commercial_partner_id.razon or self.partner_id.commercial_partner_id.name or ''

    @api.onchange('cc_aut')
    def onchange_cc_aut(self):
        if not self.cc_aut:
            return {}
        if re.match("^-?[0-9]+$", self.cc_aut) != None:
            return {}
        else:
            result = {}
            result['warning'] = {
                'title': u'Número de autorización inválido',
                'message':
                u'Por favor ingrese un número de autorización válido'
            }
            self.cc_aut = ''
            return result

    @api.onchange('nit')
    def onchange_nit(self):
        result = {}
        if self.nit and re.match("^-?[0-9]+$", self.nit) is None:
            result['warning'] = {
                'title': u'NIT inválido',
                'message': u'Por favor ingrese un número de NIT válido'
            }
            self.nit = ''
            return result
        bol_customer_pool = self.env['bol.customer']
        if not self.partner_id:
            razon_bol = bol_customer_pool.get_razon(self.nit)
            if razon_bol:
                self.razon = razon_bol
        return result

    @api.onchange('cc_dos')
    def onchange_cc_dos(self):
        if self.cc_dos:
            self.cc_aut = self.cc_dos.nro_orden

    @api.onchange('cc_cod')
    def onchange_cc_cod(self):
        if self.cc_cod:
            tam = len(self.cc_cod)
            if tam == 12:
                tam_cod = self.cc_cod[:-1]
                self.cc_cod = tam_cod
            # self.cc_aut = self.cc_dos.nro_orden

    @api.multi
    def invoice_validate(self):
        ret = super(AccountInvoice, self).invoice_validate()
        for obj_inv in self:
            if obj_inv.type in ('out_invoice', 'out_refund'):
                if not obj_inv.cc_dos and obj_inv.type == 'out_invoice':
                    raise UserError(
                        u'[BOL] Factura de venta sin serie de Dosificación.')
                if not obj_inv.company_id.allow_invoice_defer and obj_inv.date_invoice < fields.Date.context_today(
                        self):
                    raise UserError(
                        u'[BOL] No esta permitida la creación de Facturas de venta con fecha anterior.'
                    )
                if obj_inv.cc_dos.fecha_fin and obj_inv.date_invoice > obj_inv.cc_dos.fecha_fin:
                    raise UserError(
                        u'[BOL] Fecha de factura mayor a fecha limite de dosificación.'
                    )
                if obj_inv.cc_dos.require_taxes and not obj_inv.con_imp:
                    raise UserError(
                        u'[BOL] Esta dosificación únicamente puede ser aplicada para facturas con impuestos.'
                    )
                if obj_inv.cc_dos.auto_num:
                    nro_val = self.env[
                        'poi_bol_base.cc_dosif'].set_unique_numbering(
                            obj_inv.id, obj_inv.cc_dos.id, case='invoice')
                else:
                    nro_val = obj_inv.cc_nro
                    obj_inv.write({'cc_nro': nro_val})

                aut_val = obj_inv.cc_dos.nro_orden
                obj_inv.write({'cc_aut': aut_val})
            elif obj_inv.type == 'in_invoice':
                if obj_inv.con_imp:
                    if not obj_inv.cc_aut or not obj_inv.nit or not obj_inv.cc_nro:
                        raise UserError(
                            u'[BOL] Faltan datos de Control SIN para factura de compra.'
                        )
            if obj_inv.nit and obj_inv.razon:
                self.env['bol.customer'].set_razon(obj_inv.nit, obj_inv.razon)
            # Conciliar Notas de crédito con su factura origen si es aplicable
            if obj_inv.note_from_id and obj_inv.note_from_id.id and obj_inv.estado_fac == "V":
                base_invoice = self.browse([obj_inv.note_from_id.id])[0]
                if base_invoice.state == 'open' and base_invoice.residual >= obj_inv.amount_total:
                    # Conciliar de la misma manera que se hace en account/wizard/account_invoice_refund.py
                    to_reconcile_ids = {}
                    reconcile_ids = []
                    movelines = base_invoice.move_id.line_ids
                    to_reconcile_lines_obj = self.env['account.move.line']
                    for line in movelines:
                        if line.account_id.id == base_invoice.account_id.id:
                            # to_reconcile_lines += line
                            reconcile_ids.append(line.id)
                            to_reconcile_ids.setdefault(
                                line.account_id.id, []).append(line.id)
                        if line.reconciled:
                            line.remove_move_reconcile()
                    obj_inv.signal_workflow('invoice_open')
                    for tmpline in obj_inv.move_id.line_ids:
                        if tmpline.account_id.id == base_invoice.account_id.id:
                            # to_reconcile_lines += tmpline
                            reconcile_ids.append(tmpline.id)
                    to_reconcile_lines = to_reconcile_lines_obj.browse(
                        reconcile_ids)
                    to_reconcile_lines.reconcile()

                    # Si la conciliación de la Nota es completa, cambiar Estado a 'paid'
                    self.env.cr.commit()
                    if obj_inv.reconciled:
                        self.confirm_paid([obj_inv.id])
            # Calculo final totales segun tipo especificos de impuestos SIN
            ice_sum = 0.0
            iva_sum = 0.0
            exe_sum = 0.0
            for line in self.tax_line_ids:
                type_bol = False
                if line.tax_id:
                    type_bol = line.tax_id.type_bol
                    if type_bol == 'ice':
                        ice_sum += line.amount or 0.0
                    elif type_bol == 'iva':
                        iva_sum += line.amount or 0.0
                    elif type_bol == 'exe':
                        exe_sum += line.amount or 0.0
            # En caso de ser factura DUI evitar que calcule a 0 los valores por defecto de los impuestos
            if self.tipo_fac != '12':
                self.ice = ice_sum
                self.iva = iva_sum
                self.exe = exe_sum

    def _prepare_tax_line_vals(self, line, tax):
        vals = super(AccountInvoice, self)._prepare_tax_line_vals(line, tax)

        if vals.get('tax_id'):
            # Incorporar los identificadores SIN para poder sacar las sumatorias respectivas mas adelante y permitir la edicion dependiendo de si es 'manual'
            tax = self.env['account.tax'].browse(vals.get('tax_id'))
            vals['type_bol'] = tax.type_bol
            # si se activa el campo manual el sistema evita que se borre esa linea
            # manual lo considera cuando el usuario agraga una linea de impuesto
            # y por lo tanto no debe borrarse
            # vals['manual'] = tax.manual
            vals['price_include'] = tax.price_include

        return vals

    @api.multi
    def finalize_invoice_move_lines(self, move_lines):
        """ finalize_invoice_move_lines(move_lines) -> move_lines
            Reemplazar cuentas para rectificaciones de Impuesto fuera de período (o sea Notas de crédito)
        """
        move_lines = super(AccountInvoice,
                           self).finalize_invoice_move_lines(move_lines)
        if self.type in [
                'in_refund', 'out_refund'
        ] and (self.note_from_id or self.tipo_fac in ('5', '6', '8')):
            for line_arr in move_lines:
                line = line_arr[2]
                if 'tax_amount' in line and line['tax_amount'] > 0:
                    tax = self.env['account.tax'].search(
                        [('name', '=', line['name']),
                         ('parent_id', '=', False)],
                        limit=1)
                    if tax.account_creditnote_id:
                        line['account_id'] = tax.account_creditnote_id.id
        return move_lines

    def action_cancel(self):
        # Actualizar estado para Libros CV
        if super(AccountInvoice, self).action_cancel():
            return self.write({'estado_fac': 'na'})
        else:
            return False

    @api.multi
    def action_annul(self):
        # go from canceled state to draft state
        self.write({'estado_fac': 'A'})
        self.delete_workflow()
        return True

    def action_nota(self):
        # Lanzar asistente de Nota de crédito
        context = {}
        context['active_ids'] = self._ids
        context['invoice_id'] = len(self._ids) > 0 and self._ids[0] or False
        # Checking if there is a nota not cancelled
        # nota_id = self.search(['&',('note_from_id','in',ids),('state','not in',['cancel'])])
        # if len(nota_id) > 0:
        #    raise UserError('Error', u'Ya existe una nota de crédito activa para esta factura, si existió un error cancele primero la nota de crédito generada')
        wizard_form = {
            'name': u"Generar Nota de Crédito",
            'view_mode': 'form',
            'view_type': 'form',
            'res_model': 'poi_bol.nota.wizard',
            'type': 'ir.actions.act_window',
            'nodestroy': True,
            'domain': str([]),
            'target': 'new',
            'context': context,
        }
        return wizard_form

    def _prepare_refund(self,
                        invoice,
                        date_invoice=None,
                        date=None,
                        description=None,
                        journal_id=None):
        # Guardar número de factura reintegrada para futura referencia. Copiar datos SIN tambien
        invoice_data = super(AccountInvoice,
                             self)._prepare_refund(invoice,
                                                   date_invoice=date_invoice,
                                                   date=date,
                                                   description=description,
                                                   journal_id=journal_id)
        if invoice_data:
            invoice_data.update({
                'refunds_id': invoice.id,
                'nit': invoice.nit,
                'razon': invoice.razon,
                'estado_fac': 'na',
                'cc_dos': False,
                'cc_nro': 0,
                'tipo_fac': invoice.tipo_fac,
                # 'shop_id': invoice.shop_id.id,    #TODO: Update refund with warehouse_id
            })
        return invoice_data

    @api.multi
    def _compute_legacy_id(self):
        legacy_obj = self.env['account.invoice.legacy']
        for s in self:
            legacy_ids = legacy_obj.search([('active_id', '=', s.id)])
            if legacy_ids:
                s.legacy_id = legacy_ids.id
예제 #33
0
class accountInvoiceLine(models.Model):
    _inherit = "account.invoice.line"

    @api.one
    @api.depends('price_unit', 'discount', 'invoice_line_tax_ids', 'quantity',
                 'product_id', 'invoice_id.partner_id',
                 'invoice_id.currency_id', 'invoice_id.company_id')
    def _amount_line_with_tax(self):
        currency = self.invoice_id and self.invoice_id.currency_id or None
        price = self.price_unit * (1 - (self.discount or 0.0) / 100.0)
        taxes = False
        if self.invoice_line_tax_ids:
            taxes = self.invoice_line_tax_ids.compute_all(
                price,
                currency,
                self.quantity,
                product=self.product_id,
                partner=self.invoice_id.partner_id)
        self.price_subtotal_with_tax = taxes[
            'total_included'] if taxes else self.quantity * price

    base_tax = fields.Float(
        string='Precio efectivo',
        digits=dp.get_precision('Account'),
        help=
        u"Precio base efectivo despues de impuesto. Modificará el campo Precio unitario de manera que después de impuestos iguale el Precio efectivo."
    )
    price_subtotal_with_tax = fields.Monetary(
        string='Total',
        currency_field='company_currency_id',
        store=True,
        readonly=True,
        compute='_amount_line_with_tax',
        help="Monto total incluyendo impuestos.")

    @api.one
    @api.depends('base_tax', 'invoice_line_tax_id')
    def onchange_base_tax(self):
        """Calcular un nuevo precio inflado de manera que despues de sacarle impuestos de este monto base.
           Ejemplo: Caso retenciones cuando el proveedor cobra un monto fijo e independiente de si se le aplica Retención o no."""
        new_price = 0.0
        tot_tax = 0.0
        tax_ids = self.invoice_line_tax_id
        for itax in self.env['account.tax'].browse(tax_ids):
            if itax.child_ids:
                for ichild in itax.child_ids:
                    tot_tax = tot_tax + (itax.amount * abs(ichild.amount))
            else:
                tot_tax = tot_tax + itax.amount

        if tot_tax > 0.0 and tot_tax < 1:
            new_price = self.base_tax / (1 - tot_tax)

        if new_price > 0.0:
            self.price_unit = new_price
        else:
            return True

    def action_inverse_tax(self):

        # Lanzar asistente de Calculo de precio inverso
        context = {}
        context['active_ids'] = self.ids
        context['invoice_line_id'] = len(self.ids) > 0 and self.ids[0] or False
        view_id = self.env.ref('poi_bol_base.view_poi_bol_tax_inverse').id
        wizard_form = {
            'name': u"Cálculo precio inverso",
            'view_mode': 'form',
            'view_type': 'form',
            'view_id': view_id,
            'res_model': 'poi_bol.tax_inverse.wizard',
            'type': 'ir.actions.act_window',
            'nodestroy': True,
            'domain': str([]),
            'target': 'new',
            'context': context,
        }
        return wizard_form
예제 #34
0
파일: product.py 프로젝트: zhuhh/odoo
class Product(models.Model):
    _inherit = "product.product"

    stock_quant_ids = fields.One2many('stock.quant', 'product_id', help='Technical: used to compute quantities.')
    stock_move_ids = fields.One2many('stock.move', 'product_id', help='Technical: used to compute quantities.')
    qty_available = fields.Float(
        'Quantity On Hand', compute='_compute_quantities', search='_search_qty_available',
        digits=dp.get_precision('Product Unit of Measure'),
        help="Current quantity of products.\n"
             "In a context with a single Stock Location, this includes "
             "goods stored at this Location, or any of its children.\n"
             "In a context with a single Warehouse, this includes "
             "goods stored in the Stock Location of this Warehouse, or any "
             "of its children.\n"
             "stored in the Stock Location of the Warehouse of this Shop, "
             "or any of its children.\n"
             "Otherwise, this includes goods stored in any Stock Location "
             "with 'internal' type.")
    virtual_available = fields.Float(
        'Forecast Quantity', compute='_compute_quantities', search='_search_virtual_available',
        digits=dp.get_precision('Product Unit of Measure'),
        help="Forecast quantity (computed as Quantity On Hand "
             "- Outgoing + Incoming)\n"
             "In a context with a single Stock Location, this includes "
             "goods stored in this location, or any of its children.\n"
             "In a context with a single Warehouse, this includes "
             "goods stored in the Stock Location of this Warehouse, or any "
             "of its children.\n"
             "Otherwise, this includes goods stored in any Stock Location "
             "with 'internal' type.")
    incoming_qty = fields.Float(
        'Incoming', compute='_compute_quantities', search='_search_incoming_qty',
        digits=dp.get_precision('Product Unit of Measure'),
        help="Quantity of planned incoming products.\n"
             "In a context with a single Stock Location, this includes "
             "goods arriving to this Location, or any of its children.\n"
             "In a context with a single Warehouse, this includes "
             "goods arriving to the Stock Location of this Warehouse, or "
             "any of its children.\n"
             "Otherwise, this includes goods arriving to any Stock "
             "Location with 'internal' type.")
    outgoing_qty = fields.Float(
        'Outgoing', compute='_compute_quantities', search='_search_outgoing_qty',
        digits=dp.get_precision('Product Unit of Measure'),
        help="Quantity of planned outgoing products.\n"
             "In a context with a single Stock Location, this includes "
             "goods leaving this Location, or any of its children.\n"
             "In a context with a single Warehouse, this includes "
             "goods leaving the Stock Location of this Warehouse, or "
             "any of its children.\n"
             "Otherwise, this includes goods leaving any Stock "
             "Location with 'internal' type.")

    orderpoint_ids = fields.One2many('stock.warehouse.orderpoint', 'product_id', 'Minimum Stock Rules')
    nbr_reordering_rules = fields.Integer('Reordering Rules', compute='_compute_nbr_reordering_rules')
    reordering_min_qty = fields.Float(compute='_compute_nbr_reordering_rules')
    reordering_max_qty = fields.Float(compute='_compute_nbr_reordering_rules')

    @api.depends('stock_move_ids.product_qty', 'stock_move_ids.state')
    def _compute_quantities(self):
        res = self._compute_quantities_dict(self._context.get('lot_id'), self._context.get('owner_id'), self._context.get('package_id'), self._context.get('from_date'), self._context.get('to_date'))
        for product in self:
            product.qty_available = res[product.id]['qty_available']
            product.incoming_qty = res[product.id]['incoming_qty']
            product.outgoing_qty = res[product.id]['outgoing_qty']
            product.virtual_available = res[product.id]['virtual_available']

    def _product_available(self, field_names=None, arg=False):
        """ Compatibility method """
        return self._compute_quantities_dict(self._context.get('lot_id'), self._context.get('owner_id'), self._context.get('package_id'), self._context.get('from_date'), self._context.get('to_date'))

    def _compute_quantities_dict(self, lot_id, owner_id, package_id, from_date=False, to_date=False):
        domain_quant_loc, domain_move_in_loc, domain_move_out_loc = self._get_domain_locations()
        domain_quant = [('product_id', 'in', self.ids)] + domain_quant_loc
        dates_in_the_past = False
        # only to_date as to_date will correspond to qty_available
        to_date = fields.Datetime.to_datetime(to_date)
        if to_date and to_date < fields.Datetime.now():
            dates_in_the_past = True

        domain_move_in = [('product_id', 'in', self.ids)] + domain_move_in_loc
        domain_move_out = [('product_id', 'in', self.ids)] + domain_move_out_loc
        if lot_id is not None:
            domain_quant += [('lot_id', '=', lot_id)]
        if owner_id is not None:
            domain_quant += [('owner_id', '=', owner_id)]
            domain_move_in += [('restrict_partner_id', '=', owner_id)]
            domain_move_out += [('restrict_partner_id', '=', owner_id)]
        if package_id is not None:
            domain_quant += [('package_id', '=', package_id)]
        if dates_in_the_past:
            domain_move_in_done = list(domain_move_in)
            domain_move_out_done = list(domain_move_out)
        if from_date:
            domain_move_in += [('date', '>=', from_date)]
            domain_move_out += [('date', '>=', from_date)]
        if to_date:
            domain_move_in += [('date', '<=', to_date)]
            domain_move_out += [('date', '<=', to_date)]

        Move = self.env['stock.move']
        Quant = self.env['stock.quant']
        domain_move_in_todo = [('state', 'in', ('waiting', 'confirmed', 'assigned', 'partially_available'))] + domain_move_in
        domain_move_out_todo = [('state', 'in', ('waiting', 'confirmed', 'assigned', 'partially_available'))] + domain_move_out
        moves_in_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_in_todo, ['product_id', 'product_qty'], ['product_id'], orderby='id'))
        moves_out_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_out_todo, ['product_id', 'product_qty'], ['product_id'], orderby='id'))
        quants_res = dict((item['product_id'][0], item['quantity']) for item in Quant.read_group(domain_quant, ['product_id', 'quantity'], ['product_id'], orderby='id'))
        if dates_in_the_past:
            # Calculate the moves that were done before now to calculate back in time (as most questions will be recent ones)
            domain_move_in_done = [('state', '=', 'done'), ('date', '>', to_date)] + domain_move_in_done
            domain_move_out_done = [('state', '=', 'done'), ('date', '>', to_date)] + domain_move_out_done
            moves_in_res_past = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_in_done, ['product_id', 'product_qty'], ['product_id'], orderby='id'))
            moves_out_res_past = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_out_done, ['product_id', 'product_qty'], ['product_id'], orderby='id'))

        res = dict()
        for product in self.with_context(prefetch_fields=False):
            product_id = product.id
            rounding = product.uom_id.rounding
            res[product_id] = {}
            if dates_in_the_past:
                qty_available = quants_res.get(product_id, 0.0) - moves_in_res_past.get(product_id, 0.0) + moves_out_res_past.get(product_id, 0.0)
            else:
                qty_available = quants_res.get(product_id, 0.0)
            res[product_id]['qty_available'] = float_round(qty_available, precision_rounding=rounding)
            res[product_id]['incoming_qty'] = float_round(moves_in_res.get(product_id, 0.0), precision_rounding=rounding)
            res[product_id]['outgoing_qty'] = float_round(moves_out_res.get(product_id, 0.0), precision_rounding=rounding)
            res[product_id]['virtual_available'] = float_round(
                qty_available + res[product_id]['incoming_qty'] - res[product_id]['outgoing_qty'],
                precision_rounding=rounding)

        return res

    def _get_domain_locations(self):
        '''
        Parses the context and returns a list of location_ids based on it.
        It will return all stock locations when no parameters are given
        Possible parameters are shop, warehouse, location, force_company, compute_child
        '''
        Warehouse = self.env['stock.warehouse']

        if self.env.context.get('company_owned', False):
            company_id = self.env.user.company_id.id
            return (
                [('location_id.company_id', '=', company_id), ('location_id.usage', 'in', ['internal', 'transit'])],
                [('location_id.company_id', '=', False), ('location_dest_id.company_id', '=', company_id)],
                [('location_id.company_id', '=', company_id), ('location_dest_id.company_id', '=', False),
            ])
        location_ids = []
        if self.env.context.get('location', False):
            if isinstance(self.env.context['location'], int):
                location_ids = [self.env.context['location']]
            elif isinstance(self.env.context['location'], str):
                domain = [('complete_name', 'ilike', self.env.context['location'])]
                if self.env.context.get('force_company', False):
                    domain += [('company_id', '=', self.env.context['force_company'])]
                location_ids = self.env['stock.location'].search(domain).ids
            else:
                location_ids = self.env.context['location']
        else:
            if self.env.context.get('warehouse', False):
                if isinstance(self.env.context['warehouse'], int):
                    wids = [self.env.context['warehouse']]
                elif isinstance(self.env.context['warehouse'], str):
                    domain = [('name', 'ilike', self.env.context['warehouse'])]
                    if self.env.context.get('force_company', False):
                        domain += [('company_id', '=', self.env.context['force_company'])]
                    wids = Warehouse.search(domain).ids
                else:
                    wids = self.env.context['warehouse']
            else:
                wids = Warehouse.search([]).ids

            for w in Warehouse.browse(wids):
                location_ids.append(w.view_location_id.id)
        return self._get_domain_locations_new(location_ids, company_id=self.env.context.get('force_company', False), compute_child=self.env.context.get('compute_child', True))

    def _get_domain_locations_new(self, location_ids, company_id=False, compute_child=True):
        operator = compute_child and 'child_of' or 'in'
        domain = company_id and ['&', ('company_id', '=', company_id)] or []
        locations = self.env['stock.location'].browse(location_ids)
        # TDE FIXME: should move the support of child_of + auto_join directly in expression
        hierarchical_locations = locations if operator == 'child_of' else locations.browse()
        other_locations = locations - hierarchical_locations
        loc_domain = []
        dest_loc_domain = []
        # this optimizes [('location_id', 'child_of', hierarchical_locations.ids)]
        # by avoiding the ORM to search for children locations and injecting a
        # lot of location ids into the main query
        for location in hierarchical_locations:
            loc_domain = loc_domain and ['|'] + loc_domain or loc_domain
            loc_domain.append(('location_id.parent_path', '=like', location.parent_path + '%'))
            dest_loc_domain = dest_loc_domain and ['|'] + dest_loc_domain or dest_loc_domain
            dest_loc_domain.append(('location_dest_id.parent_path', '=like', location.parent_path + '%'))
        if other_locations:
            loc_domain = loc_domain and ['|'] + loc_domain or loc_domain
            loc_domain = loc_domain + [('location_id', operator, other_locations.ids)]
            dest_loc_domain = dest_loc_domain and ['|'] + dest_loc_domain or dest_loc_domain
            dest_loc_domain = dest_loc_domain + [('location_dest_id', operator, other_locations.ids)]
        return (
            domain + loc_domain,
            domain + dest_loc_domain + ['!'] + loc_domain if loc_domain else domain + dest_loc_domain,
            domain + loc_domain + ['!'] + dest_loc_domain if dest_loc_domain else domain + loc_domain
        )

    def _search_qty_available(self, operator, value):
        # In the very specific case we want to retrieve products with stock available, we only need
        # to use the quants, not the stock moves. Therefore, we bypass the usual
        # '_search_product_quantity' method and call '_search_qty_available_new' instead. This
        # allows better performances.
        if value == 0.0 and operator == '>' and not ({'from_date', 'to_date'} & set(self.env.context.keys())):
            product_ids = self._search_qty_available_new(
                operator, value, self.env.context.get('lot_id'), self.env.context.get('owner_id'),
                self.env.context.get('package_id')
            )
            return [('id', 'in', product_ids)]
        return self._search_product_quantity(operator, value, 'qty_available')

    def _search_virtual_available(self, operator, value):
        # TDE FIXME: should probably clean the search methods
        return self._search_product_quantity(operator, value, 'virtual_available')

    def _search_incoming_qty(self, operator, value):
        # TDE FIXME: should probably clean the search methods
        return self._search_product_quantity(operator, value, 'incoming_qty')

    def _search_outgoing_qty(self, operator, value):
        # TDE FIXME: should probably clean the search methods
        return self._search_product_quantity(operator, value, 'outgoing_qty')

    def _search_product_quantity(self, operator, value, field):
        # TDE FIXME: should probably clean the search methods
        # to prevent sql injections
        if field not in ('qty_available', 'virtual_available', 'incoming_qty', 'outgoing_qty'):
            raise UserError(_('Invalid domain left operand %s') % field)
        if operator not in ('<', '>', '=', '!=', '<=', '>='):
            raise UserError(_('Invalid domain operator %s') % operator)
        if not isinstance(value, (float, int)):
            raise UserError(_('Invalid domain right operand %s') % value)

        # TODO: Still optimization possible when searching virtual quantities
        ids = []
        for product in self.with_context(prefetch_fields=False).search([]):
            if OPERATORS[operator](product[field], value):
                ids.append(product.id)
        return [('id', 'in', ids)]

    def _search_qty_available_new(self, operator, value, lot_id=False, owner_id=False, package_id=False):
        ''' Optimized method which doesn't search on stock.moves, only on stock.quants. '''
        product_ids = set()
        domain_quant = self._get_domain_locations()[0]
        if lot_id:
            domain_quant.append(('lot_id', '=', lot_id))
        if owner_id:
            domain_quant.append(('owner_id', '=', owner_id))
        if package_id:
            domain_quant.append(('package_id', '=', package_id))
        quants_groupby = self.env['stock.quant'].read_group(domain_quant, ['product_id', 'quantity'], ['product_id'], orderby='id')
        for quant in quants_groupby:
            if OPERATORS[operator](quant['quantity'], value):
                product_ids.add(quant['product_id'][0])
        return list(product_ids)

    def _compute_nbr_reordering_rules(self):
        read_group_res = self.env['stock.warehouse.orderpoint'].read_group(
            [('product_id', 'in', self.ids)],
            ['product_id', 'product_min_qty', 'product_max_qty'],
            ['product_id'])
        res = {i: {} for i in self.ids}
        for data in read_group_res:
            res[data['product_id'][0]]['nbr_reordering_rules'] = int(data['product_id_count'])
            res[data['product_id'][0]]['reordering_min_qty'] = data['product_min_qty']
            res[data['product_id'][0]]['reordering_max_qty'] = data['product_max_qty']
        for product in self:
            product.nbr_reordering_rules = res[product.id].get('nbr_reordering_rules', 0)
            product.reordering_min_qty = res[product.id].get('reordering_min_qty', 0)
            product.reordering_max_qty = res[product.id].get('reordering_max_qty', 0)

    @api.onchange('tracking')
    def onchange_tracking(self):
        products = self.filtered(lambda self: self.tracking and self.tracking != 'none')
        if products:
            unassigned_quants = self.env['stock.quant'].search_count([('product_id', 'in', products.ids), ('lot_id', '=', False), ('location_id.usage','=', 'internal')])
            if unassigned_quants:
                return {
                    'warning': {
                        'title': _('Warning!'),
                        'message': _("You have products in stock that have no lot number.  You can assign serial numbers by doing an inventory.  ")}}

    @api.model
    def view_header_get(self, view_id, view_type):
        res = super(Product, self).view_header_get(view_id, view_type)
        if not res and self._context.get('active_id') and self._context.get('active_model') == 'stock.location':
            res = '%s%s' % (_('Products: '), self.env['stock.location'].browse(self._context['active_id']).name)
        return res

    @api.model
    def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
        res = super(Product, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
        if self._context.get('location') and isinstance(self._context['location'], int):
            location = self.env['stock.location'].browse(self._context['location'])
            fields = res.get('fields')
            if fields:
                if location.usage == 'supplier':
                    if fields.get('virtual_available'):
                        res['fields']['virtual_available']['string'] = _('Future Receipts')
                    if fields.get('qty_available'):
                        res['fields']['qty_available']['string'] = _('Received Qty')
                elif location.usage == 'internal':
                    if fields.get('virtual_available'):
                        res['fields']['virtual_available']['string'] = _('Forecasted Quantity')
                elif location.usage == 'customer':
                    if fields.get('virtual_available'):
                        res['fields']['virtual_available']['string'] = _('Future Deliveries')
                    if fields.get('qty_available'):
                        res['fields']['qty_available']['string'] = _('Delivered Qty')
                elif location.usage == 'inventory':
                    if fields.get('virtual_available'):
                        res['fields']['virtual_available']['string'] = _('Future P&L')
                    if fields.get('qty_available'):
                        res['fields']['qty_available']['string'] = _('P&L Qty')
                elif location.usage == 'production':
                    if fields.get('virtual_available'):
                        res['fields']['virtual_available']['string'] = _('Future Productions')
                    if fields.get('qty_available'):
                        res['fields']['qty_available']['string'] = _('Produced Qty')
        return res

    def action_update_quantity_on_hand(self):
        return self.product_tmpl_id.with_context({'default_product_id': self.id}).action_update_quantity_on_hand()

    def action_view_routes(self):
        return self.mapped('product_tmpl_id').action_view_routes()

    def action_view_stock_move_lines(self):
        self.ensure_one()
        action = self.env.ref('stock.stock_move_line_action').read()[0]
        action['domain'] = [('product_id', '=', self.id)]
        return action

    def action_open_product_lot(self):
        self.ensure_one()
        action = self.env.ref('stock.action_production_lot_form').read()[0]
        action['domain'] = [('product_id', '=', self.id)]
        action['context'] = {'default_product_id': self.id}
        return action

    @api.model
    def get_theoretical_quantity(self, product_id, location_id, lot_id=None, package_id=None, owner_id=None, to_uom=None):
        product_id = self.env['product.product'].browse(product_id)
        product_id.check_access_rights('read')
        product_id.check_access_rule('read')

        location_id = self.env['stock.location'].browse(location_id)
        lot_id = self.env['stock.production.lot'].browse(lot_id)
        package_id = self.env['stock.quant.package'].browse(package_id)
        owner_id = self.env['res.partner'].browse(owner_id)
        to_uom = self.env['uom.uom'].browse(to_uom)
        quants = self.env['stock.quant']._gather(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True)
        theoretical_quantity = sum([quant.quantity for quant in quants])
        if to_uom and product_id.uom_id != to_uom:
            theoretical_quantity = product_id.uom_id._compute_quantity(theoretical_quantity, to_uom)
        return theoretical_quantity

    def write(self, values):
        res = super(Product, self).write(values)
        if 'active' in values and not values['active']:
            products = self.mapped('orderpoint_ids').filtered(lambda r: r.active).mapped('product_id')
            if products:
                msg = _('You still have some active reordering rules on this product. Please archive or delete them first.')
                msg += '\n\n'
                for product in products:
                    msg += '- %s \n' % product.display_name
                raise UserError(msg)
        return res