trytonpsk-farming/crop.py

1590 lines
57 KiB
Python

# 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 datetime import date, datetime, timedelta
import copy
from trytond.model import Workflow, ModelView, ModelSQL, fields
from trytond.pyson import Eval, If, Bool, In, Get
from trytond.transaction import Transaction
from trytond.pool import Pool
from trytond.exceptions import UserError
from trytond.report import Report
from trytond.wizard import Wizard, StateReport, StateView, Button
from trytond.modules.company import CompanyReport
STATES = {
'readonly': (Eval('state') != 'draft'),
}
MAX_WEEKS = 18
class FarmingStage(ModelSQL, ModelView):
"Farming Stage"
__name__ = "farming.stage"
name = fields.Char('Name', required=True)
class Variety(ModelSQL, ModelView):
'Variety'
__name__ = 'farming.variety'
product = fields.Many2One('product.product', 'Product', required=True)
production_rate = fields.Float('Production Rate', digits=(16, 2))
production_uom = fields.Many2One('product.uom', 'UoM')
standard_cycle = fields.Integer('Standard Cycle', help='In weeks')
stages = fields.One2Many('farming.variety.stage', 'variety', 'Stages')
location = fields.Many2One('farming.location', 'Location')
# cycles = fields.One2Many('farming.variety.cycle', 'variety', 'Cycles')
def get_rec_name(self, name=None):
if self.location:
return self.product.template.name + '\u2014' + self.location.name
else:
return self.product.template.name
class VarietyStage(ModelSQL, ModelView):
"Variety Stage"
__name__ = "farming.variety.stage"
variety = fields.Many2One('farming.variety', 'Variety')
lot = fields.Many2One('farming.location.lot', 'Lot')
sequence = fields.Integer('Sequence', required=True)
stage = fields.Many2One('farming.stage', 'Stage', required=True)
start_time = fields.Integer('Start Time', help='Harvest week',
required=True)
production_rate = fields.Float('Production Rate', digits=(3, 2))
activities = fields.One2Many('farming.variety.stage.activity', 'stage',
'Activities')
class VarietyStageActivity(ModelSQL, ModelView):
'Variety Stage Activity'
# 'farming.variety.activity' => 'farming.variety.stage.activity'
__name__ = 'farming.variety.stage.activity'
# variety = fields.Many2One('farming.variety', 'Variety')
stage = fields.Many2One('farming.variety.stage', 'Stage')
sequence = fields.Integer('Sequence', required=True)
kind = fields.Many2One('farming.activity.kind', 'Kind', required=True)
work_time = fields.Numeric('Work Time', required=True)
cost_per_hour = fields.Numeric('Cost Per Hour', digits=(16, 2))
uom = fields.Many2One('product.uom', 'Unit')
uom_time = fields.Many2One('product.uom', 'Unit of Time',
states={'required': True})
supplies = fields.One2Many('farming.variety.stage.activity.supply',
'activity', 'Supply')
time_of_realization = fields.Integer('Time of Realization', required=True,
help='In weeks')
@fields.depends('kind', 'uom')
def on_change_kind(self):
if self.kind:
self.uom = self.kind.uom.id
class VarietyStageActivitySupply(ModelSQL, ModelView):
'Variety Stage Activity Supply'
__name__ = 'farming.variety.stage.activity.supply'
# farming.variety.activity.supply => farming.variety.stage.activity.supply
activity = fields.Many2One('farming.variety.stage.activity', 'Activity')
product = fields.Many2One("product.product", "Product", required=True)
quantity = fields.Float('Quantity', required=True)
unit = fields.Many2One('product.uom', 'Unit', domain=[
If(Bool(Eval('product_uom_category')),
('category', '=', Eval('product_uom_category')),
('category', '!=', -1)),
], depends=['product_uom_category'])
product_uom_category = fields.Function(fields.Many2One(
'product.uom.category', 'Product Uom Category'),
'on_change_with_product_uom_category')
unit_price = fields.Numeric('Unit Price', digits=(16, 2))
@fields.depends('product')
def on_change_with_unit(self, name=None):
if self.product:
unit = self.product.default_uom.id
return unit
@fields.depends('product')
def on_change_with_product_uom_category(self, name=None):
if self.product:
return self.product.default_uom_category.id
else:
return None
class CropStage(Workflow, ModelSQL, ModelView):
"Crop Stage"
__name__ = "farming.crop.stage"
sequence = fields.Integer('Sequence', required=True)
crop = fields.Many2One('farming.crop', 'Crop', required=True)
stage = fields.Many2One('farming.stage', 'Stage', required=True)
week = fields.Float('Week', required=True)
effective_date = fields.Date('Effective Date')
activities = fields.One2Many('farming.crop.stage.activity', 'stage',
'Activity')
production_rate = fields.Float('Production Rate', digits=(3, 2))
quantity_planned = fields.Function(fields.Float('Qty. Planned',
digits=(6, 2)), 'get_quantity_planned')
quantity_produced = fields.Float('Qty. Produced', digits=(6, 2))
total_cost = fields.Function(fields.Numeric(
'Total Cost', digits=(16, 2)), 'get_total_cost')
losses = fields.One2Many('farming.crop.stage.loss', 'stage', 'Losses')
total_losses = fields.Function(fields.Numeric(
'Total Losses', digits=(16, 2)), 'get_total_losses')
state = fields.Selection([
('pending', 'Pending'),
('running', 'Running'),
('finished', 'Finished'),
('cancelled', 'Cancelled'),
], 'State', readonly=True, required=True)
@classmethod
def __setup__(cls):
super(CropStage, cls).__setup__()
cls._order.insert(0, ('sequence', 'ASC'))
cls._transitions |= set((
('pending', 'running'),
('running', 'finished'),
('running', 'pending'),
))
cls._buttons.update({
'pending': {
'invisible': Eval('state').in_(['pending', 'finished'])
},
'running': {
'invisible': Eval('state').in_(['finished', 'running']),
},
'finished': {
'invisible': Eval('state') != 'running',
}})
@classmethod
@ModelView.button
@Workflow.transition('pending')
def pending(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('running')
def running(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('finished')
def finished(cls, records):
pass
@staticmethod
def default_state():
return 'pending'
def get_total_losses(self, name=None):
return sum(lo.quantity for lo in self.losses)
def get_total_cost(self, name=None):
return sum(act.total_cost or 0 for act in self.activities)
def get_quantity_planned(self, name=None):
res = 0
if self.production_rate and self.crop.quantity_planned:
res = (self.production_rate / 100) * self.crop.quantity_planned
return round(res, 2)
class CropStageLoss(Workflow, ModelSQL, ModelView):
'Crop Stage Loss'
__name__ = 'farming.crop.stage.loss'
stage = fields.Many2One('farming.crop.stage', 'Stage', ondelete='CASCADE')
date = fields.Date('Date', required=True)
quantity = fields.Integer('Quantity', required=True)
reason = fields.Text('Reason', required=True)
class Kind(ModelSQL, ModelView):
"Activity Kind"
__name__ = "farming.activity.kind"
name = fields.Char('Name', required=True)
activity_time = fields.Float('Act. Time', required=True)
uom = fields.Many2One('product.uom', 'Unit', required=True,
help="Unit of time, according to activity time")
class Crop(Workflow, ModelSQL, ModelView):
'Farming Crop'
__name__ = 'farming.crop'
_rec_name = 'number'
number = fields.Char('Number', readonly=True)
variety = fields.Many2One('farming.variety', 'Variety', required=True,
states=STATES)
location = fields.Many2One('farming.location', 'Location', required=True,
states=STATES)
company = fields.Many2One('company.company', 'Company', required=True,
states=STATES, domain=[('id', If(In('company',
Eval('context', {})), '=', '!='), Get(Eval('context', {}),
'company', 0)), ])
start_date = fields.Date('Start Date', states=STATES, depends=['state'])
end_date = fields.Date('End Date', states=STATES, depends=['state'])
field_size = fields.Float('Field Size', states=STATES, select=True,
help='In hectares')
total_plants = fields.Integer('Total Plants')
quantity_planned = fields.Function(fields.Float('Quantity Planned'),
'get_quantity_planned')
quantity_planned_net = fields.Function(fields.Float('Net. Quantity Planned'),
'get_quantity_planned_net')
quantity_produced = fields.Float('Quantity Produced', states=STATES)
production_uom = fields.Many2One('product.uom', 'Production UoM', states=STATES)
lots = fields.One2Many('farming.crop.lot', 'crop', 'Lots', states=STATES)
stages = fields.One2Many('farming.crop.stage', 'crop', 'Stages',
states=STATES)
seed = fields.Many2One('farming.seed', 'Seed', required=True)
analytic_account = fields.Many2One('analytic_account.account', 'Analytic Account')
production_rate = fields.Float('Production Rate')
notes = fields.Text('Notes', states=STATES)
state = fields.Selection([
('draft', 'Draft'),
('production', 'Production'),
('finished', 'Finished'),
('cancelled', 'Cancelled'),
], 'State', readonly=True, required=True)
# moves for supplies
# supplies = fields.One2Many('farming.crop.stage.activity.supply',
# 'crop', 'Supply')
# Financial indicators
production_time = fields.Function(fields.Integer('Production Time'),
'get_production_time')
production_cost = fields.Function(fields.Numeric('Production Cost'),
'get_production_cost')
performance = fields.Function(fields.Float('Performance', digits=(4, 2)),
'get_performance')
gross_profit_rate = fields.Function(fields.Float('Gross Profit Rate'),
'get_gross_profit_rate')
gross_profit = fields.Function(fields.Numeric('Gross Profit'),
'get_gross_profit')
# Deprecation warning: activities was moved to farming.crop.stage
# activities = fields.One2Many('farming.crop.activity', 'crop', 'Activities')
total_cost = fields.Function(fields.Numeric(
'Total Cost', digits=(16, 2)), 'get_total_cost')
total_losses = fields.Function(fields.Numeric(
'Total Losses', digits=(16, 2)), 'get_total_losses')
@classmethod
def __setup__(cls):
super(Crop, cls).__setup__()
cls._order.insert(0, ('create_date', 'DESC'))
cls._order.insert(1, ('id', 'DESC'))
cls._transitions |= set((
('draft', 'production'),
('production', 'draft'),
('production', 'finished'),
('production', 'cancelled'),
))
cls._buttons.update({
'draft': {
'invisible': Eval('state').in_(['draft', 'finished'])
},
'cancel': {
'invisible': Eval('state') == 'finished',
},
'load_stages': {
'invisible': Eval('lines', []) | (Eval('state') != 'draft'),
'depends': ['state'],
},
'load_lots': {
'invisible': Eval('lines', []) | (Eval('state') != 'draft'),
'depends': ['state'],
},
'production': {
'invisible': Eval('state').in_(['cancelled', 'finished']),
},
'finished': {
'invisible': Eval('state') != 'production',
},
})
@staticmethod
def default_start_date():
return date.today()
@staticmethod
def default_company():
return Transaction().context.get('company') or False
@staticmethod
def default_state():
return 'draft'
@classmethod
@ModelView.button
@Workflow.transition('finished')
def finished(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
def cancel(cls, services):
pass
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('production')
def production(cls, records):
for record in records:
record.set_number()
def get_total_cost(self, name=None):
return sum(act.total_cost or 0 for act in self.stages)
def get_total_losses(self, name=None):
return sum(st.total_losses for st in self.stages)
@classmethod
def clear_bed_lines(cls, crops):
Line = Pool().get('farming.crop.lot.bed')
lines_to_delete = []
for crop in crops:
for lot in crop.lots:
for bed in lot.beds:
lines_to_delete.append(bed)
Line.delete(lines_to_delete)
@classmethod
def create_beds_lines(cls, crops):
Line = Pool().get('farming.crop.lot.bed')
lines = []
for crop in crops:
for lot in crop.lots:
for bed in lot.lot.beds:
line = Line(
lot=lot.id,
bed=bed.id,
quantity_plants=bed.quantity,
)
lines.append(line)
Line.save(lines)
@classmethod
def create_lots_lines(cls, crops):
Line = Pool().get('farming.crop.lot')
Lot = Pool().get('farming.location.lot')
lines = []
cls.clear_bed_lines(crops)
cls.clear_lots_lines(crops)
for crop in crops:
lots = Lot.search([('location', '=', crop.location)])
for lot in lots:
line = Line(
crop=crop.id,
lot=lot.id,
cycle=0,
beds=[],
activities=[],
total_plants=lot.quantity
)
lines.append(line)
Line.save(lines)
cls.create_beds_lines(crops)
@classmethod
def clear_lots_lines(cls, crops):
Line = Pool().get('farming.crop.lot')
lines_to_delete = []
for crop in crops:
for lot in crop.lots:
lines_to_delete.append(lot)
Line.delete(lines_to_delete)
def _create_supplies(self, activity, crop_activity):
Supply = Pool().get('farming.crop.stage.activity.supply')
res = []
for sup in activity.supplies:
unit_price = sup.unit_price
if not unit_price:
unit_price = sup.product.cost_price
qty = sup.quantity * self.total_plants
amount = round(qty * float(unit_price), 2)
supply = Supply(
activity=crop_activity.id,
product=sup.product.id,
unit=sup.unit.id,
unit_price=unit_price,
quantity=qty,
cost_planned=amount,
cost_real=amount
)
res.append(supply)
return res
def _create_activities(self, stage, crop_stage):
Activity = Pool().get('farming.crop.stage.activity')
res = []
crop = crop_stage.crop
for act in stage.activities:
_date = crop.start_date + timedelta(weeks=act.time_of_realization)
work_time = act.work_time * crop.total_plants
# if act.uom_time.symbol == 'min':
# _cost = round((act.cost_per_hour / 60) * work_time, 2)
# elif act.uom_time.symbol == 'h':
# _cost = round(act.cost_per_hour * work_time, 2)
# elif act.uom_time.symbol == 's':
# _cost = round((act.cost_per_hour / 3600) * work_time, 2)
# else:
# _cost = round(act.cost_per_hour * work_time, 2)
activity = Activity(
stage=crop_stage.id,
sequence=act.sequence,
kind=act.kind.id,
work_time=work_time,
uom_time=act.uom_time.id,
week=act.time_of_realization,
planned_date=_date,
effective_date=_date,
)
supplies = self._create_supplies(act, activity)
activity.supplies = supplies
res.append(activity)
return res
def create_stages(self):
CropStage = Pool().get('farming.crop.stage')
to_save = []
for stage in self.variety.stages:
effec_date = self.start_date + timedelta(weeks=stage.start_time)
_stage = CropStage(
sequence=stage.sequence,
crop=self.id,
stage=stage.stage.id,
production_rate=stage.production_rate,
week=stage.start_time,
effective_date=effec_date,
)
_stage.activities = self._create_activities(stage, _stage)
to_save.append(_stage)
CropStage.save(to_save)
@classmethod
def clear_stages_lines(cls, crops):
Line = Pool().get('farming.crop.stage')
to_delete = []
for crop in crops:
for stage in crop.stages:
to_delete.append(stage)
Line.delete(to_delete)
# @classmethod
# def clear_activities_lines(cls, crops):
# Line = Pool().get('farming.crop.stage.activity')
# lines_to_delete = []
# for crop in crops:
# for activity in crop.activities:
# lines_to_delete.append(activity)
# Line.delete(lines_to_delete)
# @classmethod
# def create_activities_lines(cls, crops):
# Line = Pool().get('farming.crop.activity')
# lines = []
# for crop in crops:
# for act in crop.variety.activities:
# _date = crop.start_date + timedelta(
# weeks=act.time_of_realization)
# work_time = act.work_time * crop.total_plants
# if act.uom_time.symbol == 'min':
# _cost = round((act.cost_per_hour / 60) * work_time, 2)
# elif act.uom_time.symbol == 'h':
# _cost = round(act.cost_per_hour * work_time, 2)
# elif act.uom_time.symbol == 's':
# _cost = round((act.cost_per_hour / 3600) * work_time, 2)
# else:
# _cost = round(act.cost_per_hour * work_time, 2)
#
# line = Line(
# crop=crop.id,
# sequence=act.sequence,
# kind=act.kind.id,
# work_time=work_time,
# workforce_cost=_cost,
# uom=act.uom.id,
# uom_time=act.uom_time.id,
# week=act.time_of_realization,
# planned_date=_date,
# effective_date=_date,
# )
# lines.append(line)
# Line.save(lines)
# cls.create_activity_supplies_lines(crops)
@classmethod
def create_activity_supplies_lines(cls, crops):
Line = Pool().get('farming.crop.stage.activity.supply')
lines = []
for crop in crops:
for act_crop, act_variety in zip(
crop.activities, crop.variety.activities):
for supply in act_variety.supplies:
line = Line(
activity=act_crop.id,
product=supply.product.id,
unit=supply.unit,
quantity=supply.quantity * crop.total_plants,
cost_planned=supply.product.cost_price,
cost_real=supply.product.cost_price
)
lines.append(line)
Line.save(lines)
@classmethod
def clear_supplies_lines(cls, crops):
Line = Pool().get('farming.crop.lot.bed')
lines_to_delete = []
for crop in crops:
for stage in crop.stages:
for activity in stage.activities:
for supply in activity.supplies:
lines_to_delete.append(supply)
Line.delete(lines_to_delete)
@classmethod
def get_total_plants(cls, crops):
Line = Pool().get('farming.crop')
lines = []
for crop in crops:
quantity = 0
for lot in crop.lots:
quantity += lot.total_plants
crop.total_plants = quantity
lines.append(crop)
Line.save(lines)
@classmethod
@ModelView.button
def load_stages(cls, crops):
cls.clear_stages_lines(crops)
for crop in crops:
crop.create_stages()
# cls.clear_stages_lines(crops)
# cls.clear_supplies_lines(crops)
# cls.clear_activities_lines(crops)
# cls.create_activities_lines(crops)
@classmethod
@ModelView.button
def load_lots(cls, crops):
cls.create_lots_lines(crops)
cls.get_total_plants(crops)
@classmethod
def supplies_shipment(cls, records):
pool = Pool()
SupplyMoves = pool.get('crop.supply.moves')
supplyMoves = []
for record in records:
for activity in record.activities:
for supply in activity.supplies:
supplyMove = SupplyMoves(
crop=record.id,
product=supply.product,
unit=supply.unit,
quantity=supply.quantity,
)
supplyMoves.append(supplyMove)
SupplyMoves.save(supplyMoves)
def set_number(self):
Config = Pool().get('farming.configuration')
config = Config.get_config()
if not config.farming_production_sequence:
raise UserError('missing_sequence_farming_production')
number = config.farming_production_sequence.get()
self.write([self], {'number': number})
def get_quantity_planned_net(self, name=None):
return self.quantity_planned - (
self.total_losses * self.production_rate)
def get_quantity_planned(self, name=None):
res = []
_append = res.append
for lot in self.lots:
if self.production_rate and lot.total_plants:
_append(self.production_rate * lot.total_plants)
return round(sum(res), 2)
def get_production_time(self, name=None):
res = None
if self.start_date:
today = date.today()
if self.end_date:
res = (self.end_date - self.start_date).days
else:
res = (today - self.start_date).days
return res
def get_production_cost(self, name):
res = 0
return res
def get_performance(self, name):
res = 0
if self.quantity_produced and self.quantity_planned_net:
res = (self.quantity_produced / self.quantity_planned_net) * 100
return res
def get_gross_profit_rate(self, name):
res = 0
return res
def get_gross_profit(self, name):
res = 0
return res
@fields.depends('stages')
def on_change_stages(self):
if self.stages:
res = []
for stage in self.stages:
res.append(stage.quantity_produced or 0)
self.quantity_produced = sum(res)
@fields.depends('variety', 'production_uom', 'production_rate')
def on_change_variety(self):
if self.variety and self.variety.production_uom:
self.production_uom = self.variety.production_uom.id
self.production_rate = self.variety.production_rate
if self.variety.standard_cycle:
self.end_date = date.today() + timedelta(
weeks=self.variety.standard_cycle)
@fields.depends('start_date', 'variety')
def on_change_start_date(self):
if self.variety:
self.end_date = self.start_date + timedelta(
weeks=self.variety.standard_cycle)
@fields.depends('lots')
def on_change_with_total_plants(self, name=None):
quantity = 0
for lot in self.lots:
if lot.total_plants:
quantity += lot.total_plants
return quantity
class SupplyMoves(ModelSQL, ModelView):
'Supply moves'
__name__ = 'crop.supply.moves'
crop = fields.Many2One('farming.crop', 'Crop', ondelete='CASCADE',
required=True)
product = fields.Many2One("product.product", "Product", required=True,
select=True)
unit = fields.Many2One('product.uom', 'Unit')
quantity = fields.Float('Quantity', required=True)
origin = fields.Reference('Origin', selection='get_origin', select=True)
@classmethod
def _get_origin(cls):
'Return list of Model names for origin Reference'
return ['stock.move']
@classmethod
def get_origin(cls):
Model = Pool().get('ir.model')
get_name = Model.get_name
models = cls._get_origin()
return [(None, '')] + [(m, get_name(m)) for m in models]
class FarmingCropLot(ModelSQL, ModelView):
'Farming Crop Lot'
__name__ = 'farming.crop.lot'
crop = fields.Many2One('farming.crop', 'Crop', ondelete='CASCADE',
required=True)
lot = fields.Many2One('farming.location.lot', 'Lot', required=True)
cycle = fields.Float('Cycle', digits=(16, 2), help="In weeks")
beds = fields.One2Many('farming.crop.lot.bed', 'lot', 'Beds')
activities = fields.One2Many('farming.activity', 'lot', 'Activities')
analytic_account = fields.Many2One('analytic_account.account',
'Analytic Account')
total_plants = fields.Integer('Total Plants', states={
'readonly': Bool(Eval('beds'))}, depends=['beds'])
@fields.depends('beds')
def on_change_with_total_plants(self, name=None):
quantity = 0
if self.beds:
for bed in self.beds:
if bed.quantity_plants:
quantity += bed.quantity_plants
return quantity
class FarmingCropLotBed(ModelSQL, ModelView):
'Crop Lot Bed'
__name__ = 'farming.crop.lot.bed'
lot = fields.Many2One('farming.crop.lot', 'Lot', ondelete='CASCADE',
required=True)
bed = fields.Many2One('farming.location.lot.bed', 'Bed',
ondelete='CASCADE', required=True)
quantity_plants = fields.Integer('Quantity Plants')
class CropStageActivity(Workflow, ModelSQL, ModelView):
'Crop Stage Activity'
# farming.crop.activity => farming.crop.stage.activity
__name__ = 'farming.crop.stage.activity'
# crop = fields.Many2One('farming.crop', 'Crop', ondelete='CASCADE')
stage = fields.Many2One('farming.crop.stage', 'Stage', ondelete='CASCADE')
sequence = fields.Char('Sequence', states=STATES)
kind = fields.Many2One('farming.activity.kind', 'Kind')
work_time = fields.Numeric('Work Time')
# uom = fields.Many2One('product.uom', 'Unit')
uom_time = fields.Many2One('product.uom', 'UoM of Time')
supplies = fields.One2Many('farming.crop.stage.activity.supply',
'activity', 'Supply')
week = fields.Integer('Week')
planned_date = fields.Date('Planned Date')
effective_date = fields.Date('Effective Date')
state = fields.Selection([
('draft', 'Draft'),
('production', 'Production'),
('finished', 'Finished'),
('cancelled', 'Cancelled'),
], 'State', readonly=True, required=True)
workforce_cost = fields.Function(fields.Numeric(
'Workforce Cost', digits=(16, 2)), 'get_cost')
materials_cost = fields.Function(fields.Numeric(
'Materials Cost', digits=(16, 2)), 'get_cost')
indirect_cost = fields.Function(fields.Numeric(
'Indirect Cost', digits=(16, 2)), 'get_cost')
total_cost = fields.Function(fields.Numeric(
'Total Cost', digits=(16, 2)), 'get_cost')
@classmethod
def generate_shipment(cls, records):
pool = Pool()
company = Transaction().context.get('company')
Supply = pool.get('farming.crop.stage.activity.supply')
supplies = []
for record in records:
location = record.crop.location.warehouse
for supplyRec in record.supplies:
supply = Supply(
crop=record.crop,
product=supplyRec.product,
unit=supplyRec.unit,
quantity=supplyRec.quantity,
)
# move = record._move(
# record,
# location.output_location,
# location.production_location,
# company,
# supplyRec.product,
# supplyRec.unit,
# supplyRec.quantity)
supplies.append(supply)
Supply.save(supplies)
@classmethod
@ModelView.button
def generate_shipment_button(cls, records):
pass
def _move(self, activity, from_location, to_location, company, product,
uom, quantity):
Move = Pool().get('stock.move')
move = Move(
product=product,
uom=uom,
quantity=quantity,
from_location=from_location,
to_location=to_location,
company=company,
origin=str(activity),
state='draft'
)
Move.save([move])
return move
def get_cost(self, name=None):
res = []
for supply in self.supplies:
_name, _name_cost = name.split('_')
print(name, _name, _name_cost)
if supply.product.template.cost_type == _name or _name == 'total':
res.append(supply.cost_planned)
return sum(res)
@classmethod
def create_supply_moves(cls, location):
# pool = Pool()
# Move = pool.get('stock.move')
# Location = pool.get('stock.location')
# Date = pool.get('ir.date')
# Shipment = pool.get('stock.shipment.internal')
# moves = []
# for supply in activity.supplies:
# print(supply.unit, 'esto no es correcto')
# move = Move(
# quantity = supply.quantity,
# uom = supply.unit.id,
# product = supply.product.id,
# from_location =
# to_location =
# state = 'draft',
# company = Transaction().context.get('company'),
# unit_price = supply.product.cost_price,
# planned_date = Date.today(),
# #origin = ['production']
# )
# moves.append(move)
# shipment.moves = moves
# Shipment.save([shipment])
# #Move.save(moves)
# #BUG(ERROR: SHIPMENT INTERRUPTS STATUS, NOT GENERATED)
pass
@classmethod
def __setup__(cls):
super(CropStageActivity, cls).__setup__()
cls._order.insert(0, ('planned_date', 'ASC'))
cls._transitions |= set((
('draft', 'production'),
('production', 'draft'),
('production', 'finished'),
('production', 'cancelled'),
('finished', 'production'), # only developings
))
cls._buttons.update({
'draft': {
'invisible': Eval('state').in_(['draft', 'finished'])
},
'cancel': {
'invisible': Eval('state') == 'finished',
},
'load_configuration': {
'invisible': Eval('lines', []) | (Eval('state') != 'draft'),
'depends': ['state'],
},
'production': {},
'finished': {
'invisible': Eval('state') != 'production',
},
})
@staticmethod
def default_state():
return 'draft'
@classmethod
@ModelView.button
@Workflow.transition('finished')
def finished(cls, records):
pool = Pool()
AccountMove = pool.get('account.move')
Journal = pool.get('account.journal')
journal = Journal.search([('type', '=', 'expense')])
for record in records:
account_move = {
'company': Transaction().context.get('company'),
'journal': journal[0].id,
'date': date.today(),
'origin': str(record.crop),
'lines': None,
}
account_move_lines = []
for supply in record.supplies:
product = supply.product
account = product.account_category
product_name = product.name
lineC = {
'debit': Decimal(0),
'credit': product.cost_price,
'account': account.account_stock.id,
'description': product_name,
}
lineD = {
'debit': product.cost_price,
'credit': Decimal(0),
'account': account.account_expense.id,
'description': product_name,
'analytic_lines': [('create', [{
'debit': product.cost_price,
'credit': Decimal(0),
'account': supply.analytic_account,
'date': date.today()
}])]
}
account_move_lines.append(lineC)
account_move_lines.append(lineD)
account_move['lines'] = [('create', account_move_lines)]
AccountMove.create([account_move])
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
def cancel(cls, services):
pass
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('production')
def production(cls, records):
for record in records:
#cls.create_shipment(record)
cls.generate_shipment([record])
record.set_number()
def set_number(self):
#Config = Pool().get('farming.configuration')
#config = Config.get_config()
#if not config.farming_production_sequence:
# raise UserError('missing_sequence_farming_production')
pass
class CropStageActivitySupply(ModelSQL, ModelView):
'Crop Stage Activity Supply'
# farming.crop.activity.supply => farming.crop.stage.activity.supply
__name__ = 'farming.crop.stage.activity.supply'
# crop = fields.Many2One('farming.crop', 'Crop')
activity = fields.Many2One('farming.crop.stage.activity', 'Activity',
states={"invisible": False})
product = fields.Many2One("product.product", "Product", required=True,
select=True)
unit = fields.Many2One('product.uom', 'Unit')
quantity = fields.Float('Quantity', required=True)
unit_price = fields.Numeric('Unit Price')
analytic_account = fields.Many2One('analytic_account.account',
'Analytic Account')
cost_planned = fields.Numeric('Cost Planned', readonly=True)
cost_real = fields.Numeric('Cost Real')
# def get_unit(self, name=None):
# unit = self.product.default_uom.name
# return unit
class Activity(Workflow, ModelSQL, ModelView):
'Activity'
__name__ = 'farming.activity'
_rec_name = 'kind'
sequence = fields.Char('Sequence', states=STATES)
lot = fields.Many2One('farming.crop.lot', 'Lot', required=True,
states=STATES)
employee = fields.Many2One('party.party', 'Employee', states=STATES)
planned_date = fields.Date('Planned Date', states=STATES, required=True)
start_date = fields.Date('Start Date', states=STATES, required=True)
end_date = fields.Date('End Date', states=STATES)
kind = fields.Many2One('farming.activity.kind', 'Kind', states=STATES)
notes = fields.Text('Notes', states=STATES)
days = fields.Function(fields.Integer('Days', states=STATES), 'get_days')
shipments = fields.Many2Many('farming.activity-shipment.internal',
'activity', 'shipment', 'Shipments', states=STATES)
state = fields.Selection([
('draft', 'Draft'),
('execution', 'Execution'),
('done', 'Done'),
('cancelled', 'Cancelled'),
], 'State', readonly=True, required=True)
amount = fields.Function(fields.Numeric('Amount', digits=(16, 2)),
'get_amount')
@classmethod
def __setup__(cls):
super(Activity, cls).__setup__()
cls._order.insert(0, ('planned_date', 'DESC'))
cls._order.insert(1, ('id', 'DESC'))
cls._transitions |= set((
('draft', 'execution'),
('execution', 'done'),
('execution', 'draft'),
('execution', 'cancelled'),
('cancelled', 'draft'),
))
cls._buttons.update({
'draft': {
'invisible': Eval('state').in_(['draft', 'done']),
},
'cancel': {
'invisible': Eval('state').in_(['cancelled', 'done']),
},
'execution': {
'invisible': Eval('state') != 'draft',
},
'done': {
'invisible': Eval('state') != 'execution',
},
})
@staticmethod
def default_company():
return Transaction().context.get('company') or False
@staticmethod
def default_state():
return 'draft'
@classmethod
@ModelView.button
@Workflow.transition('done')
def done(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('execution')
def execution(cls, records):
for record in records:
record.set_number()
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
def cancel(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, records):
pass
def get_amount(self, name):
res = 0
for shipment in self.shipments:
for m in shipment.outgoing_moves:
res += m.product.template.cost_price * Decimal(m.quantity)
for w in self.works:
res += w.unit_price * Decimal(w.quantity)
return res
def get_days(self, name):
res = 0
if self.start_date and self.end_date:
res = (self.end_date - self.start_date).days
return res
class CropForecastStart(ModelView):
'Crop Forecast Start'
__name__ = 'farming.crop_forecast.start'
company = fields.Many2One('company.company', 'Company', required=True)
start_date = fields.Date("Start Date", required=True)
end_date = fields.Date("End Date", required=True)
@staticmethod
def default_company():
return Transaction().context.get('company')
class CropForecast(Wizard):
'Crop Forecast'
__name__ = 'farming.crop_forecast'
start = StateView(
'farming.crop_forecast.start',
'farming.farming_crop_forecast_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('farming.crop_forecast.report')
def do_print_(self, action):
data = {
'company': self.start.company.id,
'start_date': self.start.start_date,
}
return action, data
def transition_print_(self):
return 'end'
class CropForecastReport(Report):
'Crop Forecast Report'
__name__ = 'farming.crop_forecast.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Crop = pool.get('farming.crop')
Company = pool.get('company.company')
recs = {}
week_range = []
for nd in range(MAX_WEEKS):
week_n = 'day' + str((nd + 1))
first_date = data['start_date'] + timedelta(nd * 7)
last_date = first_date + timedelta(6)
data[week_n] = first_date.strftime("%d %b")
data['total_' + week_n] = 0
data['week' + str(nd + 1)] = first_date.strftime("%W")
recs[week_n] = ''
recs[first_date] = {
'week': week_n
}
week_range.append((first_date, last_date))
crops = Crop.search([
('start_date', '<=', data['start_date']),
('end_date', '>=', data['start_date']),
])
records = {}
for crop in crops:
records[crop] = []
start_date = crop.start_date
recs_days = copy.deepcopy(recs)
for stage in crop.stages:
prod_date = start_date + timedelta(days=stage.week * 7)
for (start_day, end_day) in week_range:
if prod_date >= start_day and prod_date < end_day:
week_n = recs_days[start_day]['week']
recs_days[week_n] = int(stage.quantity_planned)
data['total_' + week_n] += int(stage.quantity_planned)
records[crop] = recs_days
report_context['records'] = records.items()
company_id = Transaction().context.get('company')
report_context['company'] = Company(company_id)
return report_context
class FarmingActivityShipmentInternal(ModelSQL):
'Farming Activity - Shipment Internal'
__name__ = 'farming.activity-shipment.internal'
_table = 'farming_activity_shipment_internal_rel'
activity = fields.Many2One('farming.activity', 'Activity',
ondelete='CASCADE', select=True, required=True)
shipment = fields.Many2One('stock.shipment.internal', 'Tax',
ondelete='RESTRICT', required=True)
class ActivityTask(ModelView, ModelSQL):
"Execute Activity"
__name__ = 'crop.activity.task'
STATES = {
'readonly': Eval('state') == 'finished',
}
effective_date = fields.Date('Effective Date ', states=STATES,
required=True)
employee = fields.Many2One('company.employee', 'Employee', states=STATES)
quantity = fields.Float('Quantity', states=STATES, required=True)
start_time = fields.DateTime('Start Time')
production = fields.Many2One('production', 'Production', states=STATES)
state = fields.Selection([
('draft', 'Draft'),
('processing', 'Processing'),
('finished', 'Finished'),
], 'State', select=True)
@staticmethod
def default_state():
return 'draft'
class CropActivitiesStart(ModelView):
'Crop Activities Start'
__name__ = 'farming.crop_activities.start'
company = fields.Many2One('company.company', 'Company', required=True)
crop = fields.Many2One('farming.crop', 'Crop')
start_date = fields.Date("Start Date")
location = fields.Many2One('farming.location', 'Location')
@staticmethod
def default_company():
return Transaction().context.get('company')
def default_start_date():
return datetime.today()
class CropActivitiesWizard(Wizard):
'Crop Activities'
__name__ = 'farming.crop_activities'
start = StateView(
'farming.crop_activities.start',
'farming.farming_crop_activities_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('farming.crop_activities.report')
def do_print_(self, action):
location_id = None
if self.start.location:
location_id = self.start.location.id
data = {
'company': self.start.company.id,
'start_date': self.start.start_date,
'location': location_id,
}
return action, data
def transition_print_(self):
return 'end'
class CropActivitiesReport(Report):
'Crop Activities Report'
__name__ = 'farming.crop_activities.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
RANGE_WEEKS = 17
pool = Pool()
Company = pool.get('company.company')
Crop = pool.get('farming.crop')
date_start = data['start_date']
date_end = data['start_date'] + timedelta(days=(7*17))
dom = [
('activities.planned_date', '>=', date_start),
('activities.planned_date', '<=', date_end),
]
if data['location']:
dom.append(('location', '=', data['location']))
days = {}
matrix_dates = []
_date = date_start
for nd in range(1, RANGE_WEEKS):
sday = str(nd)
nday = 'day' + sday
days[nday] = ''
days['activity' + sday] = ''
data['week' + sday] = _date.strftime("%W")
data[nday] = _date.strftime("%d %b")
data['total_' + nday] = 0
last_date = _date + timedelta(days=1)
_date = _date + timedelta(days=7)
matrix_dates.append((last_date, _date, sday))
records = []
crops = Crop.search(dom)
for crop in crops:
lots_beds = []
for lot in crop.lots:
beds = '-'.join(bed.bed.number for bed in lot.beds)
lots_beds.append((lot.lot.number, beds))
# lots_beds = f'{lot.lot.number} ({beds})'
record = {
'number': crop.number,
'variety': crop.variety.product.name,
'location': crop.location.name,
'plants': crop.total_plants,
'lots': lots_beds,
'activities': [],
}
variety_acts = {
act.kind.id: act.time_of_realization
for act in crop.variety.activities
}
add_line = True
_days = copy.deepcopy(days)
for activity in crop.activities:
for start, end, sday in matrix_dates:
pdate = activity.planned_date
if pdate >= start and pdate <= end:
nday = 'day' + sday
if _days[nday] != '':
_days = copy.deepcopy(days)
record['activities'].append(_days)
elif add_line:
record['activities'].append(_days)
add_line = False
tr = variety_acts[activity.kind.id]
_days['activity' + sday] = f'{activity.kind.name}({tr})'
_days[nday] = activity.work_time
data['total_' + nday] += activity.work_time
records.append(record)
report_context['records'] = records
report_context['company'] = Company(data['company'])
return report_context
class CropSuppliesStart(ModelView):
'Crop Activities Start'
__name__ = 'farming.crop_supplies.start'
company = fields.Many2One('company.company', 'Company', required=True)
crop = fields.Many2One('farming.crop', 'Crop')
start_date = fields.Date("Start Date")
location = fields.Many2One('farming.location', 'Location', required=True)
@staticmethod
def default_company():
return Transaction().context.get('company')
def default_start_date():
return datetime.today()
class CropSuppliesWizard(Wizard):
'Crop Activities'
__name__ = 'farming.crop_supplies'
start = StateView(
'farming.crop_activities.start',
'farming.farming_crop_activities_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('farming.crop_supplies.report')
def do_print_(self, action):
data = {
#'crop': self.start.crop.id,
'company': self.start.company.id,
'start_date': self.start.start_date,
'location': self.start.location.id,
#'end_date': self.start.end_date,
}
return action, data
def transition_print_(self):
return 'end'
class CropSuppliesReport(Report):
'Crop Activities Report'
__name__ = 'farming.crop_supplies.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
date_start = date(data['start_date'].year, data['start_date'].month, 1)
calculate_month = data['start_date'] + timedelta(days=30)
date_end = date(data['start_date'].year, calculate_month.month, 28)
week_list = []
date_calculate_week = date_start + timedelta(7)
for w in range(13):
week_data = {
'number': date_calculate_week.isocalendar()[1],
'range': date_calculate_week
}
week_list.append(week_data)
date_calculate_week = date_calculate_week + timedelta(days=7)
Location = pool.get('farming.location')
locations = Location.search([('id', '=', data['location'])])
# location_list = []
for location in locations:
Crop = pool.get('farming.crop')
crops = Crop.search([
('location', '=', location.id),
('end_date', '>=', date_end),
('activities', '!=', None)
])
supplies_list = []
for crop in crops:
for activity in crop.activities:
for supply in activity.supplies:
supply_record = supply.product.name
supplies_list.append(supply_record)
supplies_list = set(supplies_list)
supplies_list = [{'supply': spl} for spl in supplies_list]
crop_list = []
for crop in crops:
for activity in crop.activities:
activity_week = activity.planned_date.isocalendar()[1]
supply_info_list = []
for week in week_list:
spl = {
'quantity': '',
'unit': '',
}
supply_info_list.append(spl)
for supply in activity.supplies:
for index, week in enumerate(week_list):
week_number = week['number']
if activity_week == week_number:
for spl in supplies_list:
if spl['supply'] == supply.product.name:
supply_info_list[index-1] = {
'name': supply.product.name,
'quantity': supply.quantity,
'unit': supply.unit
}
#spl['info'] = supply_info_list
break
suply_crop = {
'crop': crop.number,
'info': supply_info_list
}
crop_list.append(suply_crop)
report_context['weeks'] = week_list
report_context['supplies'] = supplies_list
report_context['company'] = Company(data['company'])
report_context['date_start'] = date_start
report_context['date_end'] = date_end
return report_context
class CropForecastWeekStart(ModelView):
'Crop Activities Start'
__name__ = 'farming.crop_forecast_week.start'
company = fields.Many2One('company.company', 'Company', required=True)
crop = fields.Many2One('farming.crop', 'Crop')
start_date = fields.Date("Start Date")
location = fields.Many2One('farming.location', 'Location')
@staticmethod
def default_company():
return Transaction().context.get('company')
def default_start_date():
return datetime.today()
class CropForecastWeekWizard(Wizard):
'Crop Activities'
__name__ = 'farming.crop_forecast_week'
start = StateView(
'farming.crop_forecast_week.start',
'farming.farming_crop_forecast_week_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('farming.crop_forecast_week.report')
def do_print_(self, action):
data = {
# 'crop': self.start.crop.id,
'company': self.start.company.id,
'start_date': self.start.start_date,
'location': self.start.location.id if self.start.location else self.start.location,
# 'end_date': self.start.end_date,
}
return action, data
def transition_print_(self):
return 'end'
class CropForecastWeekReport(Report):
'Crop Activities Report'
__name__ = 'farming.crop_forecast_week.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
date_start = date(data['start_date'].year, data['start_date'].month, 1)
calculate_month = data['start_date'] + timedelta(days=30)
date_end = date(data['start_date'].year, calculate_month.month, 28)
week_list = []
date_calculate_week = date_start
for w in range(13):
week_data = {
'number': date_calculate_week.isocalendar()[1],
'range': date_calculate_week
}
week_list.append(week_data)
date_calculate_week = date_calculate_week + timedelta(days=7)
Location = pool.get('farming.location')
if data['location']:
locations = Location.search([('id', '=', data['location'])])
else:
locations = Location.search([('name', '!=', '')])
location_list = []
for location in locations:
Crop = pool.get('farming.crop')
crops = Crop.search([
('location', '=', location.id),
('end_date', '>=', date_end),
('activities', '!=', None)
])
crop_list = []
total_list = [0 for x in range(13)]
for crop in crops:
crop_report = {
'lots': []
}
lots_report = []
for lot in crop.lots:
beds = ''
for bed in lot.beds:
beds = beds + '-' + bed.bed.number
lot_dict = {
'number': crop.number,
'variety': crop.variety.product.name,
'lot': 'Lote: ' + lot.lot.number,
'total_plants': lot.total_plants,
'beds': 'Camas: ' + beds,
'stages': []
}
lots_report.append(lot_dict)
crop_report['lots'] = lots_report
Stage = pool.get('farming.crop.stage')
stages = Stage.search(['crop', "=", crop.id])
for lot_report in lots_report:
stage_list = []
for stage in stages:
stage_week = stage.effective_date.isocalendar()[1]
for week in week_list:
act = {
'name': '',
'production_rate': 0
}
stage_list.append(act)
for index, week in enumerate(week_list):
week_number = week['number']
if stage_week == week_number:
stage_list[index-1] = {
'name': stage.stage.name,
'production_rate': int(
stage.production_rate * lot_report['total_plants'] * crop.production_rate * .01)
}
total_list[index-1] = int(total_list[index-1] + (stage.production_rate * lot_report['total_plants'] * crop.production_rate * .01))
break
lot_report['stages'] = stage_list
crop_list.append(crop_report)
location_list.append({'name': location.name, 'crops': crop_list, 'total_time': total_list})
report_context['weeks'] = week_list
report_context['records'] = location_list
report_context['company'] = Company(data['company'])
report_context['date_start'] = date_start
report_context['date_end'] = date_end
return report_context
class FarmingCropReport(CompanyReport):
__name__ = 'farming.crop'