Active Record
This commit is contained in:
1 parent
0105ea3c38
commit
760eff2bfe
3 files changed
+93
-97
No files matched your search
+8
-1
@@ -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')
|
||||
@@ -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)
|
||||
+23
-30
@@ -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()
|
||||
Reference in new issue
Block a user