trytond-production_operation/operation.py

320 lines
11 KiB
Python
Raw Normal View History

2013-10-30 23:30:06 +01:00
from decimal import Decimal
from trytond.model import fields, ModelSQL, ModelView, Workflow
2014-01-03 13:10:49 +01:00
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval, If, Bool, Id
2014-01-03 13:10:49 +01:00
from trytond.transaction import Transaction
2013-10-30 23:30:06 +01:00
2014-01-03 13:10:49 +01:00
__all__ = ['Operation', 'OperationTracking', 'Production']
2013-10-30 23:30:06 +01:00
__metaclass__ = PoolMeta
2014-01-03 13:10:49 +01:00
STATES = {
'readonly': Eval('state').in_(['running', 'done'])
}
DEPENDS = ['state']
2013-10-30 23:30:06 +01:00
2014-01-03 13:10:49 +01:00
class Operation(Workflow, ModelSQL, ModelView):
'Operation'
__name__ = 'production.operation'
production = fields.Many2One('production', 'Production', required=True,
states=STATES, depends=DEPENDS)
sequence = fields.Integer('Sequence', states=STATES, depends=DEPENDS)
2014-01-04 19:36:34 +01:00
work_center_category = fields.Many2One('production.work_center.category',
'Work Center Category', states=STATES, depends=DEPENDS, required=True)
2014-01-03 13:10:49 +01:00
work_center = fields.Many2One('production.work_center', 'Work Center',
states=STATES, depends=DEPENDS + ['work_center_category'], domain=[
('category', '=', Eval('work_center_category'),
)])
2014-01-03 13:10:49 +01:00
route_operation = fields.Many2One('production.route.operation',
'Route Operation', states=STATES, depends=DEPENDS)
lines = fields.One2Many('production.operation.tracking', 'operation',
'Lines', states=STATES, depends=DEPENDS, context={
'work_center_category': Eval('work_center_category'),
2014-01-03 13:10:49 +01:00
'work_center': Eval('work_center'),
})
cost = fields.Function(fields.Numeric('Cost'), 'get_cost')
operation_type = fields.Many2One('production.operation.type',
'Operation Type', states=STATES, depends=DEPENDS, required=True)
uom_category = fields.Function(fields.Many2One(
'product.uom.category', 'Uom Category', on_change_with=[
'work_center_category', 'work_center']),
2014-01-03 13:10:49 +01:00
'on_change_with_uom_category')
state = fields.Selection([
('planned', 'Planned'),
('waiting', 'Waiting'),
('running', 'Running'),
('done', 'Done'),
], 'State', readonly=True)
2013-10-30 23:30:06 +01:00
@classmethod
def __setup__(cls):
2014-01-03 13:10:49 +01:00
super(Operation, cls).__setup__()
2013-10-30 23:30:06 +01:00
cls._order.insert(0, ('sequence', 'ASC'))
cls._invalid_production_states_on_create = ['done']
2014-01-03 13:10:49 +01:00
cls._error_messages.update({
'invalid_production_state': ('You can not create an operation'
' for Production "%s".'),
})
cls._transitions |= set((
('planned', 'waiting'),
('waiting', 'running'),
('running', 'waiting'),
('running', 'done'),
))
cls._buttons.update({
'wait': {
'invisible': ~Eval('state').in_(['planned', 'running']),
'icon': If(Eval('state') == 'running',
'tryton-go-previous', 'tryton-go-next')
2014-01-03 13:10:49 +01:00
},
'run': {
'invisible': Eval('state') != 'waiting',
'icon': 'tryton-go-next',
},
'done': {
'invisible': Eval('state') != 'running',
},
})
2013-10-30 23:30:06 +01:00
@staticmethod
2014-01-03 13:10:49 +01:00
def default_state():
return 'planned'
2013-10-30 23:30:06 +01:00
2014-01-03 13:10:49 +01:00
def get_rec_name(self, name):
if self.operation_type:
return self.operation_type.rec_name
return super(Operation, self).get_rec_name()
2013-10-30 23:30:06 +01:00
2014-01-03 13:10:49 +01:00
@classmethod
def search_rec_name(cls, name, clause):
return [('operation_type.name',) + tuple(clause[1:])]
2013-10-30 23:30:06 +01:00
2014-01-03 13:10:49 +01:00
def on_change_with_uom_category(self, name=None):
if self.work_center_category:
return self.work_center_category.uom.category.id
2014-01-03 13:10:49 +01:00
if self.work_center:
return self.work_center.uom.category.id
2013-10-30 23:30:06 +01:00
@classmethod
2014-01-03 13:10:49 +01:00
def create(cls, vlist):
pool = Pool()
Production = pool.get('production')
productions = []
for value in vlist:
productions.append(value['production'])
invalid_productions = Production.search([
('id', 'in', productions),
('state', 'in', cls._invalid_production_states_on_create),
], limit=1)
if invalid_productions:
production, = invalid_productions
cls.raise_user_error('invalid_production_state',
production.rec_name)
return super(Operation, cls).create(vlist)
@classmethod
def copy(cls, operations, default=None):
if default is None:
default = {}
default.setdefault('state', 'planned')
default.setdefault('lines', [])
return super(Operation, cls).copy(operations, default)
2013-10-30 23:30:06 +01:00
@staticmethod
def order_sequence(tables):
table, _ = tables[None]
return [table.sequence == None, table.sequence]
def get_cost(self, name):
2014-01-03 13:10:49 +01:00
cost = Decimal('0.0')
for line in self.lines:
cost += line.cost
return cost
@classmethod
@ModelView.button
@Workflow.transition('waiting')
def wait(cls, operations):
pass
@classmethod
@ModelView.button
@Workflow.transition('running')
def run(cls, operations):
pass
@classmethod
def done(cls, operations):
pool = Pool()
Production = pool.get('production')
2013-10-30 23:30:06 +01:00
2014-01-03 13:10:49 +01:00
productions = set([o.production for o in operations])
cls.write(operations, {'state': 'done'})
with Transaction().set_context(from_done_operation=True):
Production.done(productions)
class OperationTracking(ModelSQL, ModelView):
'operation.tracking'
__name__ = 'production.operation.tracking'
2013-10-30 23:30:06 +01:00
operation = fields.Many2One('production.operation', 'Operation',
required=True)
2014-01-03 13:10:49 +01:00
uom = fields.Many2One('product.uom', 'Uom', required=True,
on_change_with=['operation'], domain=[
('category', '=', Id('product', 'uom_cat_time')),
])
2014-01-03 13:10:49 +01:00
unit_digits = fields.Function(fields.Integer('Unit Digits',
on_change_with=['uom']), 'on_change_with_unit_digits')
quantity = fields.Float('Quantity', required=True,
digits=(16, Eval('unit_digits', 2)), depends=['unit_digits'])
2013-10-30 23:30:06 +01:00
cost = fields.Function(fields.Numeric('Cost'), 'get_cost')
2014-01-03 13:10:49 +01:00
@staticmethod
def default_quantity():
return 0.0
@staticmethod
def default_uom():
WorkCenter = Pool().get('production.work_center')
WorkCenterCategory = Pool().get('production.work_center.category')
2014-01-03 13:10:49 +01:00
context = Transaction().context
if context.get('work_center'):
2014-01-03 13:10:49 +01:00
work_center = WorkCenter(context['work_center'])
return work_center.uom.id
if context.get('work_center_category'):
category = WorkCenterCategory(context['work_center_category'])
return category.uom.id
2014-01-03 13:10:49 +01:00
2013-10-30 23:30:06 +01:00
def get_cost(self, name):
Uom = Pool().get('product.uom')
work_center = (self.operation.work_center or
self.operation.work_center_category)
2014-01-03 13:10:49 +01:00
if not work_center:
return Decimal('0.0')
quantity = Uom.compute_qty(self.uom, self.quantity,
work_center.uom)
return Decimal(str(quantity)) * work_center.cost_price
def on_change_with_uom(self):
if self.operation and self.operation.work_center:
2014-01-03 13:10:49 +01:00
return self.operation.work_center.uom.id
2013-10-30 23:30:06 +01:00
2014-01-03 13:10:49 +01:00
def on_change_with_unit_digits(self, name=None):
if self.uom:
return self.uom.digits
return 2
2013-10-30 23:30:06 +01:00
class Production:
__name__ = 'production'
2014-01-03 13:10:49 +01:00
2013-10-30 23:30:06 +01:00
route = fields.Many2One('production.route', 'Route',
2014-01-03 13:10:49 +01:00
on_change=['route', 'operations'], states={
'readonly': ~Eval('state').in_(['request', 'draft']),
})
2013-10-30 23:30:06 +01:00
operations = fields.One2Many('production.operation', 'production',
2014-01-03 13:10:49 +01:00
'Operations', order=[('sequence', 'ASC')], states={
'readonly': Eval('state') == 'done',
})
@classmethod
def __setup__(cls):
super(Production, cls).__setup__()
cls._error_messages.update({
'pending_operations': ('Production "%(production)s" can not be'
' done because their operation "%(operation)s" is not '
'done.'),
'no_work_center': ('We can not found any work center for '
'Operation "%s".'),
})
2013-10-30 23:30:06 +01:00
def update_operations(self):
if not self.route:
return {}
operations = {
'remove': [x.id for x in self.operations],
'add': [],
}
changes = {
'operations': operations,
}
for operation in self.route.operations:
operations['add'].append({
'sequence': operation.sequence,
'work_center_category': operation.work_center_category.id,
2013-10-30 23:30:06 +01:00
'work_center': (operation.work_center.id
if operation.work_center else None),
2014-01-03 13:10:49 +01:00
'operation_type': (operation.operation_type.id
if operation.operation_type else None),
2013-10-30 23:30:06 +01:00
'route_operation': operation.id,
})
return changes
def on_change_route(self):
return self.update_operations()
2014-01-03 13:10:49 +01:00
@classmethod
def run(cls, productions):
pool = Pool()
Operation = pool.get('production.operation')
super(Production, cls).run(productions)
operations = []
for production in productions:
operations.extend(production.operations)
if operations:
Operation.wait(operations)
@classmethod
def done(cls, productions):
pool = Pool()
Operation = pool.get('production.operation')
Template = pool.get('product.template')
Product = pool.get('product.product')
pending_operations = Operation.search([
('production', 'in', [p.id for p in productions]),
('state', '!=', 'done'),
], limit=1)
if pending_operations:
if Transaction().context.get('from_done_operation', False):
return
operation, = pending_operations
cls.raise_user_error('pending_operations', error_args={
'production': operation.production.rec_name,
'operation': operation.rec_name,
})
if hasattr(Product, 'cost_price'):
digits = Product.cost_price.digits
else:
digits = Template.cost_price.digits
for production in productions:
operation_cost = sum(o.cost for o in production.operations)
if operation_cost == Decimal('0.0'):
continue
2014-01-03 13:10:49 +01:00
total_quantity = Decimal(str(sum(o.quantity for o in
production.outputs)))
added_unit_price = Decimal(operation_cost / total_quantity
).quantize(Decimal(str(10 ** -digits[1])))
for output in production.outputs:
output.unit_price += added_unit_price
output.save()
super(Production, cls).done(productions)
2013-10-30 23:30:06 +01:00
def get_cost(self, name):
cost = super(Production, self).get_cost(name)
for operation in self.operations:
cost += operation.cost
return cost