trytonpsk-farming/crop.py

490 lines
17 KiB
Python
Raw Normal View History

2021-11-02 18:25:11 +01:00
# 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
from trytond.model import Workflow, ModelView, ModelSQL, fields
from trytond.pyson import Eval, If, In, Get
from trytond.transaction import Transaction
from trytond.pool import Pool
from trytond.exceptions import UserError
2022-08-16 06:50:49 +02:00
from trytond.report import Report
from trytond.wizard import (Wizard, StateReport, StateView,
Button, StateTransition)
2021-11-02 18:25:11 +01:00
STATES = {
'readonly': (Eval('state') != 'draft'),
}
2022-08-19 05:05:25 +02:00
class FarmingStage(ModelSQL, ModelView):
"Farming Stage"
__name__ = "farming.stage"
name = fields.Char('Name', required=True)
class FarmingVarietyCycle(ModelSQL, ModelView):
"Farming Variety Cycle"
__name__ = "farming.variety.cycle"
variety = fields.Many2One('farming.variety', 'Variety', required=True)
2022-08-19 16:59:05 +02:00
sequence = fields.Integer('Sequence', required=True)
2022-08-19 05:05:25 +02:00
stage = fields.Many2One('farming.stage', 'Stage', required=True)
2022-08-19 19:10:12 +02:00
start_time = fields.Integer('Start Time', help='Harvest week',
required=True)
2022-08-19 05:05:25 +02:00
production_rate = fields.Float('Production Rate', digits=(3, 2))
2022-08-16 06:50:49 +02:00
class FarmingVariety(ModelSQL, ModelView):
'Farming Variety'
__name__ = 'farming.variety'
product = fields.Many2One('product.product', 'Product', required=True)
2022-08-19 05:05:25 +02:00
standard_cycle = fields.Integer('Standard Cycle', help='In weeks')
2022-08-16 06:50:49 +02:00
activities = fields.One2Many('farming.variety.activity', 'variety',
'Activities')
2022-08-19 05:05:25 +02:00
cycles = fields.One2Many('farming.variety.cycle', 'variety', 'Cycles')
2022-08-16 06:50:49 +02:00
def get_rec_name(self, name=None):
return self.product.template.name
class FarmingVarietyActivity(ModelSQL, ModelView):
'Farming Variety Activity'
__name__ = 'farming.variety.activity'
variety = fields.Many2One('farming.variety', 'Variety', required=True)
sequence = fields.Integer('Sequence', required=True)
kind = fields.Many2One('farming.activity.kind', 'Kind', required=True)
time_of_realization = fields.Integer('Time Of Realization', required=True,
help='In weeks')
2022-08-19 16:59:05 +02:00
class CropStage(ModelSQL, ModelView):
"Crop Stage"
__name__ = "farming.crop.stage"
crop = fields.Many2One('farming.crop', 'Crop', required=True)
stage = fields.Many2One('farming.stage', 'Stage', required=True)
activity_time = fields.Float('Act. Time', required=True)
2021-11-02 18:25:11 +01:00
class Kind(ModelSQL, ModelView):
"Kind"
__name__ = "farming.activity.kind"
name = fields.Char('Name', required=True)
activity_time = fields.Float('Act. Time', required=True)
2022-08-16 06:50:49 +02:00
class FarmingCrop(Workflow, ModelSQL, ModelView):
'Farming Crop'
__name__ = 'farming.crop'
2021-11-02 18:25:11 +01:00
_rec_name = 'number'
number = fields.Char('Number', readonly=True)
2022-08-16 06:50:49 +02:00
variety = fields.Many2One('farming.variety', 'Variety', required=True,
2021-11-02 18:25:11 +01:00
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')
2022-08-19 05:05:25 +02:00
quantity_produced_planned = fields.Function(fields.Float('Quantity Produced'),
'get_quantity_produced_planned')
2021-11-02 18:25:11 +01:00
quantity_produced = fields.Float('Quantity Produced', states=STATES)
quantity_produced_uom = fields.Many2One('product.uom', 'Quantity UoM', states=STATES)
2022-08-19 16:59:05 +02:00
lots = fields.One2Many('farming.crop.lot', 'crop', 'Lots', states=STATES)
stages = fields.One2Many('farming.crop.stage', 'crop', 'Stages',
states=STATES)
2022-08-19 05:05:25 +02:00
# seed = fields.Char('Seed', states=STATES)
seed = fields.Many2One('farming.seed', 'Seed', required=True)
2021-11-02 18:25:11 +01:00
description = fields.Char('Description', states=STATES)
notes = fields.Text('Notes', states=STATES)
state = fields.Selection([
('draft', 'Draft'),
('production', 'Production'),
('finished', 'Finished'),
('cancelled', 'Cancelled'),
], 'State', readonly=True, required=True)
# 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.Numeric('Performance'),
'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')
@classmethod
def __setup__(cls):
2022-08-16 06:50:49 +02:00
super(FarmingCrop, cls).__setup__()
2021-11-02 18:25:11 +01:00
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',
},
'production': {
'invisible': Eval('state').in_(['cancelled', 'finished']),
},
'finished': {
'invisible': Eval('state') != 'production',
},
})
@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 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})
2022-08-19 05:05:25 +02:00
def get_quantity_produced_planned(self, name=None):
res = []
_append = res.append
for lot in self.lots:
if lot.production_rate and lot.total_plants:
_append(lot.production_rate * lot.total_plants)
return sum(res)
def get_production_time(self, name=None):
2021-11-02 18:25:11 +01:00
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
return res
def get_gross_profit_rate(self, name):
res = 0
return res
def get_gross_profit(self, name):
res = 0
return res
2022-08-16 06:50:49 +02:00
class FarmingCropLot(ModelSQL, ModelView):
'Farming Crop Lot'
__name__ = 'farming.crop.lot'
_rec_name = 'lot'
2022-08-16 06:50:49 +02:00
crop = fields.Many2One('farming.crop', 'Crop', required=True)
number = fields.Char('Number', required=True)
cycle = fields.Float('Cycle', digits=(16, 2), help="In weeks")
production_rate = fields.Float('Production Rate', digits=(16, 2),
help="In units")
beds = fields.One2Many('farming.crop.lot.bed', 'lot', 'Beds')
activities = fields.One2Many('farming.activity', 'lot', 'Activities')
total_plants = fields.Function(fields.Integer('Total Plants'),
'get_total_plants')
def get_total_plants(self, name):
res = []
for bed in self.beds:
res.append(bed.quantity_plants)
return sum(res)
class FarmingCropLotBed(ModelSQL, ModelView):
'Crop Lot Bed'
__name__ = 'farming.crop.lot.bed'
_rec_name = 'code'
lot = fields.Many2One('farming.crop.lot', 'Lot', required=True)
2022-08-19 05:05:25 +02:00
bed = fields.Many2One('farming.location.lot.bed', 'Bed', required=True)
quantity_plants = fields.Integer('Quantity Plants')
2022-08-16 06:50:49 +02:00
unit = fields.Many2One('product.uom', 'Unit')
2022-07-15 09:01:45 +02:00
production_rate = fields.Float('Production Rate', digits=(16, 2),
2022-08-16 06:50:49 +02:00
help="In the unit of measure")
2021-11-02 18:25:11 +01:00
class Activity(Workflow, ModelSQL, ModelView):
'Activity'
__name__ = 'farming.activity'
_rec_name = 'kind'
sequence = fields.Char('Sequence', states=STATES)
2022-08-16 06:50:49 +02:00
lot = fields.Many2One('farming.crop.lot', 'Lot', required=True,
states=STATES)
employee = fields.Many2One('party.party', 'Employee', states=STATES)
2021-11-02 18:25:11 +01:00
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)
2022-08-16 06:50:49 +02:00
# works = fields.One2Many('farming.crop.work', 'activity', 'Works',
# states=STATES)
2021-11-02 18:25:11 +01:00
state = fields.Selection([
('draft', 'Draft'),
('execution', 'Execution'),
('done', 'Done'),
('cancelled', 'Cancelled'),
], 'State', readonly=True, required=True)
2022-08-16 06:50:49 +02:00
amount = fields.Function(fields.Numeric('Amount', digits=(16, 2)),
'get_amount')
2021-11-02 18:25:11 +01:00
@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
2022-08-16 06:50:49 +02:00
class CropForecastStart(ModelView):
'Crop Forecast Start'
__name__ = 'farming.crop_forecast.start'
company = fields.Many2One('company.company', 'Company', required=True)
2022-08-17 00:46:41 +02:00
start_date = fields.Date("Start Date", required=True)
2022-08-16 06:50:49 +02:00
# storage = fields.Many2One('stock.location', 'Location', required=True,
# domain=[('type', '=', 'storage')])
2021-11-02 18:25:11 +01:00
2022-08-16 06:50:49 +02:00
@staticmethod
def default_company():
return Transaction().context.get('company')
2021-11-02 18:25:11 +01:00
2022-08-16 06:50:49 +02:00
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,
2022-08-17 00:46:41 +02:00
'start_date': self.start.start_date,
2022-08-16 06:50:49 +02:00
}
return action, data
def transition_print_(self):
return 'end'
class CropForecastReport(Report):
'Crop Forecast Report'
__name__ = 'farming.crop_forecast.report'
2022-08-17 00:46:41 +02:00
MAX_DAYS = 16
2022-08-16 06:50:49 +02:00
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
2022-08-17 00:46:41 +02:00
Crop = pool.get('farming.crop')
# BookingFolio = pool.get('hotel.folio')
alldays = {}
alldays_convert = {}
for nd in range(MAX_DAYS):
day_n = 'day' + str((nd + 7))
tdate = data['start_date'] + timedelta(nd + 7)
data[day_n] = tdate
data['total_' + day_n] = 0
# data[('revenue_' + day_n)] = 0
# data[('rate_' + day_n)] = 0
alldays[day_n] = ''
alldays_convert[tdate] = day_n
date_init = data['start_date']
date_limit = data['start_date'] + timedelta(MAX_DAYS)
target_crops = data['start_date'] - timedelta(days=300)
crops = Crop.search([
('start_date', '>=', target_crops)
])
2022-08-16 06:50:49 +02:00
2022-08-17 00:46:41 +02:00
drooms = {}
for room in rooms:
drooms[room.id] = {'name': room.name}
drooms[room.id].update(alldays.copy())
for line in lines:
_delta = (line.departure_date - line.arrival_date).days
for i in range(_delta):
dt = line.arrival_date + timedelta(i)
if dt >= date_init and dt < date_limit \
and dt >= data['date']:
dayn = alldays_convert[dt]
drooms[line.room.id][dayn] = "X"
data['total_' + dayn] += 1
data['revenue_' + dayn] += float(line.unit_price) / 1000000
for i in range(MAX_DAYS):
day_n = 'day' + str((i + 1))
data['rate_' + day_n] = (data['total_' + day_n] * 100.0) / len(rooms)
report_context['records'] = list(drooms.values())
2022-08-16 06:50:49 +02:00
report_context['date'] = data['date']
2022-08-17 00:46:41 +02:00
report_context['company'] = Company(data['company']).party.name
2022-08-16 06:50:49 +02:00
return report_context
# class FarmingWork(ModelSQL, ModelView):
# 'Farming Work'
# __name__ = 'farming.crop.work'
# sequence = fields.Integer('Sequence', select=True)
# activity = fields.Many2One('farming.activity', 'Activity', select=True,
# ondelete='CASCADE', required=True)
# method = fields.Selection([
# ('by_day', 'By Day'),
# ('by_productivity', 'By Productivity'),
# ], 'Method', required=True)
# quantity = fields.Float('Quantity', required=True)
# unit_price = fields.Numeric('Unit Price', digits=(16, 2), required=True)
# amount = fields.Function(fields.Numeric('Amount', digits=(16, 2)), 'get_amount')
# employee = fields.Many2One('company.employee', 'Employee')
#
# @classmethod
# def __setup__(cls):
# super(FarmingWork, cls).__setup__()
#
# def get_amount(self, name):
# res = 0
# if self.quantity and self.unit_price:
# res = Decimal(self.quantity) * self.unit_price
# return res
#
# @staticmethod
# def default_method():
# return 'by_day'
2021-11-02 18:25:11 +01:00
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)