2014-05-07 14:10:58 +02:00
|
|
|
# The COPYRIGHT file at the top level of this repository contains the full
|
|
|
|
# copyright notices and license terms.
|
|
|
|
from datetime import datetime
|
|
|
|
from decimal import Decimal
|
|
|
|
|
|
|
|
from trytond.model import fields
|
|
|
|
from trytond.pool import Pool, PoolMeta
|
|
|
|
from trytond.pyson import Bool, Eval, Id, Not, Or
|
|
|
|
from trytond.transaction import Transaction
|
|
|
|
|
|
|
|
__all__ = ['MoveEvent', 'Sale', 'SaleLine']
|
|
|
|
__metaclass__ = PoolMeta
|
|
|
|
|
|
|
|
|
|
|
|
class MoveEvent:
|
|
|
|
__name__ = 'farm.move.event'
|
|
|
|
|
|
|
|
origin = fields.Reference('Origin', selection='get_origin', readonly=True,
|
|
|
|
select=True)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __setup__(cls):
|
|
|
|
super(MoveEvent, cls).__setup__()
|
|
|
|
cls._error_messages.update({
|
|
|
|
'weight_required_in_sale_move_event': (
|
|
|
|
'The weigth in the Move Event "%s" is required because it '
|
|
|
|
'is a move of a sale.'),
|
|
|
|
})
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _get_origin(cls):
|
|
|
|
'Return list of Model names for origin Reference'
|
|
|
|
return ['sale.line']
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_origin(cls):
|
|
|
|
pool = Pool()
|
|
|
|
Model = pool.get('ir.model')
|
|
|
|
models = cls._get_origin()
|
|
|
|
models = Model.search([
|
|
|
|
('model', 'in', models),
|
|
|
|
])
|
|
|
|
return [('', '')] + [(m.model, m.name) for m in models]
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def validate_event(cls, events):
|
|
|
|
pool = Pool()
|
|
|
|
Sale = pool.get('sale.sale')
|
|
|
|
|
|
|
|
sales_to_process = set()
|
|
|
|
for event in events:
|
|
|
|
if event.origin and isinstance(event.origin, SaleLine):
|
|
|
|
if not event.weight:
|
|
|
|
cls.raise_user_error('weight_required_in_sale_move_event',
|
|
|
|
(event.rec_name,))
|
|
|
|
sales_to_process.add(event.origin.sale.id)
|
2014-05-14 16:58:02 +02:00
|
|
|
with Transaction().set_user(0, set_context=True):
|
|
|
|
super(MoveEvent, cls).validate_event(events)
|
|
|
|
if sales_to_process:
|
|
|
|
Sale.process(Sale.browse(list(sales_to_process)))
|
2014-05-07 14:10:58 +02:00
|
|
|
|
|
|
|
|
|
|
|
class Sale:
|
|
|
|
__name__ = 'sale.sale'
|
|
|
|
|
|
|
|
move_events = fields.Function(fields.One2Many('farm.move.event', None,
|
|
|
|
"Animal's Moves"),
|
|
|
|
'get_move_events')
|
|
|
|
|
|
|
|
def get_move_events(self, name):
|
|
|
|
return [m.id for l in self.lines for m in l.move_events]
|
|
|
|
|
|
|
|
def get_shipment_state(self):
|
|
|
|
state = super(Sale, self).get_shipment_state()
|
|
|
|
if state in ('none', 'sent') and self.move_events:
|
|
|
|
if all(l.move_event_done for l in self.lines):
|
|
|
|
return 'sent'
|
|
|
|
else:
|
|
|
|
return 'waiting'
|
|
|
|
return state
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def process(cls, sales):
|
|
|
|
super(Sale, cls).process(sales)
|
|
|
|
for sale in sales:
|
|
|
|
if sale.invoice_state != 'paid' or sale.shipment_state != 'sent':
|
|
|
|
continue
|
|
|
|
if not sale.move_events:
|
|
|
|
continue
|
|
|
|
for line in sale.lines:
|
|
|
|
line.set_move_event_unit_price()
|
|
|
|
|
|
|
|
def create_shipment(self, shipment_type):
|
|
|
|
res = super(Sale, self).create_shipment(shipment_type)
|
|
|
|
|
|
|
|
if self.shipment_method == 'manual':
|
|
|
|
return res
|
|
|
|
|
|
|
|
for line in self.lines:
|
|
|
|
move_event = line.get_move_event(shipment_type)
|
|
|
|
if move_event:
|
|
|
|
move_event.save()
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
|
|
class SaleLine:
|
|
|
|
__name__ = 'sale.line'
|
|
|
|
|
|
|
|
animal = fields.Reference('Animal', selection='get_animal_models', states={
|
|
|
|
'invisible': Or(Eval('type') != 'line',
|
|
|
|
Not(Eval('context', {}).get('groups', []).contains(
|
|
|
|
Id('farm', 'group_farm')))),
|
|
|
|
'readonly': ~Eval('_parent_sale', {}),
|
2015-01-22 17:54:51 +01:00
|
|
|
}, depends=['type'])
|
2014-05-07 14:10:58 +02:00
|
|
|
animal_type = fields.Function(fields.Selection([
|
|
|
|
(None, ''),
|
|
|
|
('male', 'Male'),
|
|
|
|
('female', 'Female'),
|
|
|
|
('individual', 'Individual'),
|
|
|
|
('group', 'Group'),
|
2015-01-22 17:54:51 +01:00
|
|
|
], 'Animal Type'),
|
2014-05-07 14:10:58 +02:00
|
|
|
'on_change_with_animal_type')
|
|
|
|
animal_location = fields.Many2One('stock.location', 'Animal Location',
|
|
|
|
domain=[
|
|
|
|
('type', '=', 'storage'),
|
|
|
|
], states={
|
|
|
|
'invisible': ~Bool(Eval('animal_type')),
|
|
|
|
'required': Bool(Eval('animal_type')),
|
|
|
|
'readonly': Eval('animal_type', '') != 'group',
|
2015-01-22 17:54:51 +01:00
|
|
|
}, depends=['animal_type'])
|
2014-05-07 14:10:58 +02:00
|
|
|
animal_quantity = fields.Integer('Animal Quantity', states={
|
|
|
|
'invisible': Eval('animal_type', '') != 'group',
|
|
|
|
'required': Eval('animal_type', '') == 'group',
|
|
|
|
}, depends=['animal_type'])
|
2014-05-27 14:38:08 +02:00
|
|
|
move_events = fields.One2Many('farm.move.event', 'origin',
|
|
|
|
"Animal's Moves", readonly=True)
|
2014-05-07 14:10:58 +02:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __setup__(cls):
|
|
|
|
super(SaleLine, cls).__setup__()
|
|
|
|
if hasattr(cls, 'analytic_accounts'):
|
2015-01-22 17:54:51 +01:00
|
|
|
cls.animal.on_change.add('analytic_accounts')
|
|
|
|
cls.animal_location.on_change.add('analytic_accounts')
|
2014-05-07 14:10:58 +02:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_animal_models(cls):
|
|
|
|
IrModel = Pool().get('ir.model')
|
|
|
|
models = IrModel.search([
|
|
|
|
('model', 'in', [
|
|
|
|
'farm.animal',
|
|
|
|
'farm.animal.group',
|
|
|
|
]),
|
|
|
|
])
|
|
|
|
return [('', '')] + [(m.model, m.name) for m in models]
|
|
|
|
|
2015-01-22 17:54:51 +01:00
|
|
|
@fields.depends('type', 'animal', '_parent_sale.sale_date')
|
2014-05-07 14:10:58 +02:00
|
|
|
def on_change_animal(self):
|
|
|
|
Animal = Pool().get('farm.animal')
|
|
|
|
|
|
|
|
res = {
|
|
|
|
'animal_location': None,
|
|
|
|
'animal_quantity': None,
|
|
|
|
}
|
|
|
|
if (not self.type or self.type != 'line' or not self.animal or
|
|
|
|
self.animal.id < 0):
|
|
|
|
return res
|
|
|
|
|
|
|
|
analytic_accounts = None
|
|
|
|
if isinstance(self.animal, Animal):
|
|
|
|
res = {
|
|
|
|
'animal_location': (self.animal.location.id
|
|
|
|
if self.animal.location else None),
|
|
|
|
'animal_quantity': 1,
|
|
|
|
}
|
|
|
|
if hasattr(self.animal.location, 'analytic_accounts'):
|
|
|
|
analytic_accounts = self.animal.location.analytic_accounts
|
|
|
|
elif len(self.animal.locations) == 1:
|
|
|
|
group_location = self.animal.locations[0]
|
|
|
|
with Transaction().set_context(
|
|
|
|
locations=[group_location.id],
|
|
|
|
stock_date_end=(self.sale.sale_date
|
|
|
|
if self.sale and self.sale.sale_date else None)):
|
|
|
|
res = {
|
|
|
|
'animal_location': group_location.id,
|
|
|
|
'animal_quantity': self.animal.quantity or None,
|
|
|
|
}
|
|
|
|
if hasattr(group_location, 'analytic_accounts'):
|
|
|
|
analytic_accounts = group_location.analytic_accounts
|
|
|
|
|
|
|
|
if (res['animal_location'] and hasattr(self, 'analytic_accounts') and
|
|
|
|
analytic_accounts):
|
|
|
|
for account in analytic_accounts.accounts:
|
|
|
|
res['analytic_account_%s' % account.root.id] = account.id
|
|
|
|
return res
|
|
|
|
|
2015-01-22 17:54:51 +01:00
|
|
|
@fields.depends('type', 'animal')
|
2014-05-07 14:10:58 +02:00
|
|
|
def on_change_with_animal_type(self, name=None):
|
|
|
|
Animal = Pool().get('farm.animal')
|
|
|
|
if not self.type or not self.animal or self.animal.id < 0:
|
|
|
|
return None
|
|
|
|
if isinstance(self.animal, Animal):
|
|
|
|
return self.animal.type
|
|
|
|
return 'group'
|
|
|
|
|
2015-01-22 17:54:51 +01:00
|
|
|
@fields.depends('animal', 'animal_location', '_parent_sale.sale_date')
|
2014-05-07 14:10:58 +02:00
|
|
|
def on_change_animal_location(self):
|
|
|
|
if not self.animal or not self.animal_location:
|
|
|
|
return {
|
|
|
|
'animal_quantity': None,
|
|
|
|
}
|
|
|
|
with Transaction().set_context(
|
|
|
|
locations=[self.animal_location.id],
|
|
|
|
stock_date_end=(self.sale.sale_date
|
|
|
|
if self.sale and self.sale.sale_date else None)):
|
|
|
|
res = {
|
|
|
|
'animal_quantity': self.animal.lot.quantity or None,
|
|
|
|
}
|
|
|
|
if (hasattr(self, 'analytic_accounts') and
|
|
|
|
self.animal_location.analytic_accounts):
|
|
|
|
for account in self.animal_location.analytic_accounts.accounts:
|
|
|
|
res['analytic_account_%s' % account.root.id] = account.id
|
|
|
|
return res
|
|
|
|
|
|
|
|
@property
|
|
|
|
def move_event_done(self):
|
|
|
|
if not self.animal_type:
|
|
|
|
return True
|
|
|
|
|
|
|
|
quantity = self.animal_quantity
|
|
|
|
for event in self.move_events:
|
|
|
|
if event.state == 'draft':
|
|
|
|
return False
|
|
|
|
if event.state == 'validated':
|
|
|
|
quantity -= event.quantity
|
|
|
|
if (self.animal_quantity > 0 and quantity > 0 or
|
|
|
|
self.animal_quantity < 0 and quantity < 0):
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
def get_move_event(self, shipment_type):
|
|
|
|
'''
|
|
|
|
Return move event for the sale line according ot shipment_type
|
|
|
|
'''
|
|
|
|
pool = Pool()
|
|
|
|
MoveEvent = pool.get('farm.move.event')
|
|
|
|
|
|
|
|
if self.type != 'line':
|
|
|
|
return
|
|
|
|
if not self.animal_type:
|
|
|
|
return
|
|
|
|
if (shipment_type == 'out') != (self.animal_quantity >= 0):
|
|
|
|
return
|
|
|
|
|
|
|
|
quantity = self.animal_quantity
|
|
|
|
for event in self.move_events:
|
|
|
|
if event.state != 'cancel':
|
|
|
|
quantity -= event.quantity
|
|
|
|
if (self.animal_quantity > 0 and quantity <= 0 or
|
|
|
|
self.animal_quantity < 0 and quantity >= 0):
|
|
|
|
return
|
|
|
|
|
|
|
|
if not self.sale.party.customer_location:
|
|
|
|
self.raise_user_error('customer_location_required', {
|
|
|
|
'sale': self.sale.rec_name,
|
|
|
|
'line': self.rec_name,
|
|
|
|
})
|
|
|
|
|
|
|
|
with Transaction().set_user(0, set_context=True):
|
|
|
|
move_event = MoveEvent()
|
|
|
|
move_event.animal_type = self.animal_type
|
|
|
|
move_event.specie = self.animal.specie
|
|
|
|
move_event.farm = self.animal_location.warehouse
|
|
|
|
if self.animal_type == 'group':
|
|
|
|
move_event.animal_group = self.animal
|
|
|
|
else:
|
|
|
|
move_event.animal = self.animal
|
|
|
|
move_event.timestamp = datetime.combine(self.delivery_date,
|
|
|
|
datetime.now().time()) if self.delivery_date else datetime.now()
|
|
|
|
move_event.from_location = self.animal_location
|
|
|
|
move_event.to_location = self.sale.party.customer_location
|
|
|
|
move_event.quantity = self.animal_quantity
|
|
|
|
move_event.unit_price = Decimal('0.0')
|
|
|
|
move_event.origin = self
|
|
|
|
return move_event
|
|
|
|
|
|
|
|
def set_move_event_unit_price(self):
|
|
|
|
pool = Pool()
|
|
|
|
ModelData = pool.get('ir.model.data')
|
|
|
|
LotCostLine = pool.get('stock.lot.cost_line')
|
|
|
|
|
|
|
|
invoice_amount = sum(il.on_change_with_amount()
|
|
|
|
for il in self.invoice_lines if il.invoice.paid)
|
|
|
|
delivered_animals = sum(e.quantity for e in self.move_events
|
|
|
|
if e.state == 'validated')
|
2015-10-29 12:04:13 +01:00
|
|
|
unit_price = self.sale.currency.round(invoice_amount
|
|
|
|
/ delivered_animals)
|
2014-05-07 14:10:58 +02:00
|
|
|
|
2014-05-20 17:27:35 +02:00
|
|
|
category_id = ModelData.get_id('sale_farm', 'cost_category_sale_price')
|
2014-05-07 14:10:58 +02:00
|
|
|
lot = self.animal.lot
|
|
|
|
for event in self.move_events:
|
|
|
|
if event.state != 'validated':
|
|
|
|
continue
|
|
|
|
event.unit_price = unit_price
|
|
|
|
event.save()
|
|
|
|
|
|
|
|
if lot.cost_price != unit_price:
|
|
|
|
cost_line = LotCostLine()
|
|
|
|
cost_line.lot = lot
|
|
|
|
cost_line.category = category_id
|
|
|
|
cost_line.origin = str(event)
|
2015-10-29 12:04:13 +01:00
|
|
|
cost_line.unit_price = self.sale.currency.round(
|
|
|
|
unit_price - lot.cost_price
|
2014-05-07 14:10:58 +02:00
|
|
|
if lot.cost_price else unit_price)
|
|
|
|
cost_line.save()
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def copy(cls, lines, default=None):
|
|
|
|
if default is None:
|
|
|
|
default = {}
|
|
|
|
else:
|
|
|
|
default = default.copy()
|
|
|
|
default['move_events'] = None
|
|
|
|
return super(SaleLine, cls).copy(lines, default=default)
|