From 760eff2bfe7a6e6684a4f98a4b70a103bbe3ac2f Mon Sep 17 00:00:00 2001 From: Oscar Andres Alvarez Date: Mon, 24 Sep 2012 22:09:23 +0200 Subject: [PATCH] Active Record --- __init__.py | 9 +++- move.py | 128 +++++++++++++++++++++++++--------------------------- product.py | 53 ++++++++++------------ 3 files changed, 93 insertions(+), 97 deletions(-) diff --git a/__init__.py b/__init__.py index 6487779d89..431d17e30e 100644 --- a/__init__.py +++ b/__init__.py @@ -1,5 +1,12 @@ #This file is part of Tryton. The COPYRIGHT file at the top level of #this repository contains the full copyright notices and license terms. - +from trytond.pool import Pool from .product import * from .move import * + + +def register(): + Pool.register( + Template, + Move, + module='product_cost_fifo', type_='model') diff --git a/move.py b/move.py index 2ae8060fd8..65c5a3e4f8 100644 --- a/move.py +++ b/move.py @@ -1,73 +1,69 @@ #This file is part of Tryton. The COPYRIGHT file at the top level of #this repository contains the full copyright notices and license terms. from decimal import Decimal -from trytond.model import ModelView, ModelSQL, fields +from trytond.model import fields from trytond.pyson import Eval -from trytond.pool import Pool +from trytond.pool import Pool, PoolMeta + +__all__ = ['Move'] +__metaclass__ = PoolMeta -class Move(ModelSQL, ModelView): - _name = 'stock.move' +class Move: + __name__ = 'stock.move' fifo_quantity = fields.Float('FIFO Quantity', digits=(16, Eval('unit_digits', 2)), depends=['unit_digits']) - def __init__(self): - super(Move, self).__init__() - self._sql_constraints += [ - ('check_fifo_quantity_out', - 'CHECK(quantity >= fifo_quantity)', - 'FIFO quantity can not be greater than quantity!'), - ] - self._error_messages.update({ - 'del_move_fifo': 'You can not delete move that is used ' \ + @classmethod + def __setup__(cls): + super(Move, cls).__setup__() + cls._sql_constraints += [ + ('check_fifo_quantity_out', + 'CHECK(quantity >= fifo_quantity)', + 'FIFO quantity can not be greater than quantity!'), + ] + cls._error_messages.update({ + 'del_move_fifo': 'You can not delete move that is used '\ 'for FIFO cost price!', - }) + }) - def default_fifo_quantity(self): + @staticmethod + def default_fifo_quantity(): return 0.0 - def _update_fifo_out_product_cost_price(self, product, quantity, uom, + @classmethod + def _update_fifo_out_product_cost_price(cls, product, quantity, uom, date): ''' Update the product cost price of the given product. Update fifo_quantity on the concerned incomming moves. Return the cost price for outputing the given product and quantity. - - :param product: a BrowseRecord of the product - :param quantity: the quantity of the outgoing product - :param uom: the uom id or a BrowseRecord of the uom - :param date: the date for the currency rate calculation - :return: cost_price (of type decimal) ''' - template_obj = Pool().get('product.template') - uom_obj = Pool().get('product.uom') + Uom = Pool().get('product.uom') - if isinstance(uom, (int, long)): - uom = uom_obj.browse(uom) - - total_qty = uom_obj.compute_qty(uom, quantity, product.default_uom, + total_qty = Uom.compute_qty(uom, quantity, product.default_uom, round=False) - fifo_moves = template_obj.get_fifo_move(product.template.id, total_qty) + fifo_moves = product.template.get_fifo_move(total_qty) cost_price = Decimal("0.0") consumed_qty = 0.0 for move, move_qty in fifo_moves: consumed_qty += move_qty - move_unit_price = uom_obj.compute_price(move.uom, move.unit_price, + move_unit_price = Uom.compute_price(move.uom, move.unit_price, move.product.default_uom) cost_price += move_unit_price * Decimal(str(move_qty)) - self._update_product_cost_price(product.id, -move_qty, + cls._update_product_cost_price(product.id, -move_qty, product.default_uom, move_unit_price, move.currency, move.company, date) - move_qty = uom_obj.compute_qty(product.default_uom, move_qty, + move_qty = Uom.compute_qty(product.default_uom, move_qty, move.uom, round=False) - self.write(move.id, { + cls.write([move], { 'fifo_quantity': (move.fifo_quantity or 0.0) + move_qty, }) @@ -75,71 +71,71 @@ class Move(ModelSQL, ModelView): cost_price = cost_price / Decimal(str(consumed_qty)) if cost_price != Decimal("0"): - digits = self.cost_price.digits + digits = cls.cost_price.digits return cost_price.quantize( Decimal(str(10.0 ** -digits[1]))) else: return product.cost_price - def create(self, vals): + @classmethod + def create(cls, vals): pool = Pool() - location_obj = pool.get('stock.location') - product_obj = pool.get('product.product') - date_obj = pool.get('ir.date') + Location = pool.get('stock.location') + Product = pool.get('product.product') + Date = pool.get('ir.date') - today = date_obj.today() + today = Date.today() effective_date = vals.get('effective_date') or today if vals.get('state') == 'done': - from_location = location_obj.browse(vals['from_location']) - to_location = location_obj.browse(vals['to_location']) - product = product_obj.browse(vals['product']) + from_location = Location(vals['from_location']) + to_location = Location(vals['to_location']) + product = Product(vals['product']) if (from_location.type in ('supplier', 'production') and to_location.type == 'storage' and product.cost_price_method == 'fifo'): - self._update_product_cost_price(vals['product'], + cls._update_product_cost_price(vals['product'], vals['quantity'], vals['uom'], vals['unit_price'], vals['currency'], effective_date) if (to_location.type == 'supplier' and from_location.type == 'storage' and product.cost_price_method == 'fifo'): - self._update_product_cost_price(vals['product'], + cls._update_product_cost_price(vals['product'], -vals['quantity'], vals['uom'], vals['unit_price'], vals['currency'], effective_date) if to_location.type != 'storage' \ and to_location.type != 'supplier' \ and product.cost_price_method == 'fifo': - cost_price = self._update_fifo_out_product_cost_price(product, + cost_price = cls._update_fifo_out_product_cost_price(product, vals['quantity'], vals['uom'], effective_date) if not vals.get('cost_price'): vals['cost_price'] = cost_price - return super(Move, self).create(vals) + return super(Move, cls).create(vals) - def write(self, ids, vals): - date_obj = Pool().get('ir.date') + @classmethod + def write(cls, moves, vals): + Date = Pool().get('ir.date') - if isinstance(ids, (int, long)): - ids = [ids] - today = date_obj.today() + today = Date.today() effective_date = vals.get('effective_date') or today if 'state' in vals and vals['state'] == 'done': - for move in self.browse(ids): + for move in moves: if vals['state'] == 'cancel': if (move.from_location.type in ('supplier', 'production') and move.to_location.type == 'storage' and move.state != 'cancel' and move.product.cost_price_method == 'fifo'): - self._update_product_cost_price(move.product.id, + cls._update_product_cost_price(move.product.id, -move.quantity, move.uom, move.unit_price, move.currency, move.company, effective_date) if (move.to_location.type == 'supplier' and move.from_location.type == 'storage' and move.state != 'cancel' and move.product.cost_price_method == 'fifo'): - self._update_product_cost_price(move.product.id, + cls._update_product_cost_price(move.product.id, move.quantity, move.uom, move.unit_price, move.currency, move.company, effective_date) @@ -148,31 +144,31 @@ class Move(ModelSQL, ModelView): and move.to_location.type == 'storage' and move.state != 'done' and move.product.cost_price_method == 'fifo'): - self._update_product_cost_price(move.product.id, + cls._update_product_cost_price(move.product.id, move.quantity, move.uom, move.unit_price, move.currency, move.company, effective_date) if (move.to_location.type == 'supplier' and move.from_location.type == 'storage' and move.state != 'done' and move.product.cost_price_method == 'fifo'): - self._update_product_cost_price(move.product.id, + cls._update_product_cost_price(move.product.id, -move.quantity, move.uom, move.unit_price, move.currency, move.company, effective_date) if move.to_location.type != 'storage' \ and move.to_location.type != 'supplier' \ and move.product.cost_price_method == 'fifo': - cost_price = self._update_fifo_out_product_cost_price( + cost_price = cls._update_fifo_out_product_cost_price( move.product, move.quantity, move.uom, effective_date) if not vals.get('cost_price'): vals['cost_price'] = cost_price - return super(Move, self).write(ids, vals) + super(Move, cls).write(moves, vals) - def delete(self, ids): - if self.search([ - ('id', 'in', ids), - ('fifo_quantity', '!=', 0.0), - ]): - self.raise_user_error('del_move_fifo') - return super(Move, self).delete(ids) -Move() + @classmethod + def delete(cls, moves): + if cls.search([ + ('id', 'in', [m.id for m in moves]), + ('fifo_quantity', '!=', 0.0), + ]): + cls.raise_user_error('del_move_fifo') + super(Move, cls).delete(moves) diff --git a/product.py b/product.py index 197574a220..f226809346 100644 --- a/product.py +++ b/product.py @@ -1,66 +1,61 @@ #This file is part of Tryton. The COPYRIGHT file at the top level of #this repository contains the full copyright notices and license terms. -import copy import datetime -from trytond.model import ModelView, ModelSQL from trytond.transaction import Transaction -from trytond.pool import Pool +from trytond.pool import Pool, PoolMeta + +__all__ = ['Template'] +__metaclass__ = PoolMeta -class Template(ModelSQL, ModelView): - _name = "product.template" +class Template: + __name__ = 'product.template' - def __init__(self): - super(Template, self).__init__() + @classmethod + def __setup__(cls): + super(Template, cls).__setup__() new_sel = ('fifo', 'FIFO') - if new_sel not in self.cost_price_method.selection: - self.cost_price_method = copy.copy(self.cost_price_method) - self.cost_price_method.selection.append(new_sel) - self._reset_columns() + if new_sel not in cls.cost_price_method.selection: + cls.cost_price_method.selection.append(new_sel) - def get_fifo_move(self, template_id, quantity=0.0): + def get_fifo_move(self, quantity=0.0): ''' Return a list of (move, qty) where move is the move to be consumed and qty is the quantity (in the product default uom) to be consumed on this move. The list contains the "first in" moves for the given quantity. - - :param template_id: the product template id - :param quantity: the quantity to be removed from the stock - :return: list of (move, qty) where move is a browse record, - qty is a float ''' pool = Pool() - move_obj = pool.get('stock.move') - uom_obj = pool.get('product.uom') - location_obj = pool.get('stock.location') + Move = pool.get('stock.move') + Uom = pool.get('product.uom') + Location = pool.get('stock.location') - locations = location_obj.search([ + locations = Location.search([ ('type', '=', 'storage'), ]) stock_date_end = datetime.date.today() - - with Transaction().set_context(locations=locations, + location_ids = [l.id for l in locations] + with Transaction().set_context(locations=location_ids, stock_date_end=stock_date_end): - template = self.browse(template_id) + template = Template(self.id) offset = 0 limit = Transaction().cursor.IN_MAX avail_qty = template.quantity fifo_moves = [] while avail_qty > 0.0: - move_ids = move_obj.search([ + moves = Move.search([ ('product.template.id', '=', template.id), ('state', '=', 'done'), ('from_location.type', '=', 'supplier'), ], offset=offset, limit=limit, order=[('effective_date', 'DESC'), ('id', 'DESC')]) - if not move_ids: + if not moves: break offset += limit - for move in move_obj.browse(move_ids): - qty = uom_obj.compute_qty(move.uom, + for move in moves: + qty = Uom.compute_qty(move.uom, move.quantity - move.fifo_quantity, template.default_uom, round=False) avail_qty -= qty @@ -76,5 +71,3 @@ class Template(ModelSQL, ModelView): fifo_moves.reverse() return fifo_moves - -Template()