Add wine aging history

046447
This commit is contained in:
Raimon Esteve 2023-03-28 17:13:43 +02:00
parent 472230a296
commit 503d1ba111
17 changed files with 358 additions and 19 deletions

View File

@ -4,6 +4,7 @@
from trytond.pool import Pool
from . import contract
from . import invoice
from . import history
from . import party
from . import plot
from . import product
@ -20,6 +21,8 @@ def register():
contract.AgronomicsContract,
contract.AgronomicsContractLine,
invoice.InvoiceLine,
history.WineAgingHistory,
history.ProductWineAgingHistory,
party.Party,
plot.Enclosure,
plot.Crop,

72
history.py Normal file
View File

@ -0,0 +1,72 @@
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
from sql import Literal, Null
from sql.aggregate import Min, Sum
from sql.functions import CurrentTimestamp
from trytond.pool import Pool
from trytond.model import ModelSQL, ModelView, fields
from trytond.pyson import Eval
from trytond.transaction import Transaction
class WineAgingHistory(ModelSQL, ModelView):
'Wine Aging History'
__name__ = 'wine.wine_aging.history'
production = fields.Many2One('production', "Production",
required=True, readonly=True)
location = fields.Many2One('stock.location', "Location", required=True,
readonly=True)
product = fields.Many2One('product.product', "Product", required=True,
readonly=True)
material = fields.Many2One('stock.location.material', "Material",
readonly=True)
date_start = fields.Date("Date Start", required=True, readonly=True)
date_end = fields.Date("Date End", readonly=True,
domain=[
['OR',
('date_end', '=', None),
('date_end', '>=', Eval('date_start')),
],
],
depends=['date_start'])
duration = fields.Integer("Duration")
@classmethod
def delete(cls, records):
pass
class ProductWineAgingHistory(ModelSQL, ModelView):
"Product Wine Aging History"
__name__ = 'product.wine.wine_aging.history'
product = fields.Many2One('product.product', "Product")
material = fields.Many2One('stock.location.material', "Material")
duration = fields.Integer("Duration")
@classmethod
def table_query(cls):
pool = Pool()
WineAgingHistory = pool.get('wine.wine_aging.history')
wine_aging_history = WineAgingHistory.__table__()
product_id = Transaction().context.get('product')
sql_where = None
if product_id:
sql_where = (wine_aging_history.product == product_id)
query = wine_aging_history.select(
(Min(wine_aging_history.id * 2)).as_('id'),
Literal(0).as_('create_uid'),
CurrentTimestamp().as_('create_date'),
cls.write_uid.sql_cast(Literal(Null)).as_('write_uid'),
cls.write_date.sql_cast(Literal(Null)).as_('write_date'),
wine_aging_history.product.as_('product'),
wine_aging_history.material.as_('material'),
Sum(wine_aging_history.date_end - wine_aging_history.date_start).as_('duration'),
group_by=[wine_aging_history.product, wine_aging_history.material])
if sql_where:
query.where = sql_where
return query

100
history.xml Normal file
View File

@ -0,0 +1,100 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<!-- wine aging history -->
<record model="ir.ui.view" id="wine_aging_history_form_view">
<field name="model">wine.wine_aging.history</field>
<field name="type">form</field>
<field name="name">wine_aging_history_form</field>
</record>
<record model="ir.ui.view" id="wine_aging_history_list_view">
<field name="model">wine.wine_aging.history</field>
<field name="type">tree</field>
<field name="name">wine_aging_history_list</field>
</record>
<record model="ir.action.act_window" id="act_wine_aging_history">
<field name="name">Wine Aging History</field>
<field name="res_model">wine.wine_aging.history</field>
</record>
<record model="ir.action.act_window.view" id="act_wine_aging_history_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="wine_aging_history_list_view"/>
<field name="act_window" ref="act_wine_aging_history"/>
</record>
<record model="ir.action.act_window.view" id="act_wine_aging_history_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="wine_aging_history_form_view"/>
<field name="act_window" ref="act_wine_aging_history"/>
</record>
<record model="ir.model.access" id="access_wine_aging_history">
<field name="model" search="[('model', '=', 'wine.wine_aging.history')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="False"/>
</record>
<menuitem name="History" sequence="0" id="menu_wine_aging_history"
parent="menu_agronomics" action="act_wine_aging_history"/>
<record model="ir.ui.menu-res.group" id="menu_wine_aging_history_group_agronomics">
<field name="menu" ref="menu_wine_aging_history"/>
<field name="group" ref="group_agronomics"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_wine_aging_history_group_agronomics_admin">
<field name="menu" ref="menu_wine_aging_history"/>
<field name="group" ref="group_agronomics_admin"/>
</record>
<!-- product wine aging history -->
<record model="ir.ui.view" id="product_wine_aging_history_form_view">
<field name="model">product.wine.wine_aging.history</field>
<field name="type">form</field>
<field name="name">product_wine_aging_history_form</field>
</record>
<record model="ir.ui.view" id="product_wine_aging_history_list_view">
<field name="model">product.wine.wine_aging.history</field>
<field name="type">tree</field>
<field name="name">product_wine_aging_history_list</field>
</record>
<record model="ir.action.act_window" id="act_product_wine_aging_history">
<field name="name">Wine Aging History</field>
<field name="res_model">product.wine.wine_aging.history</field>
</record>
<record model="ir.action.act_window.view" id="act_product_wine_aging_history_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="product_wine_aging_history_list_view"/>
<field name="act_window" ref="act_product_wine_aging_history"/>
</record>
<record model="ir.action.act_window.view" id="act_product_wine_aging_history_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="product_wine_aging_history_form_view"/>
<field name="act_window" ref="act_product_wine_aging_history"/>
</record>
<record model="ir.model.access" id="access_product_wine_aging_history">
<field name="model" search="[('model', '=', 'product.wine.wine_aging.history')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<menuitem name="Product History" sequence="0" id="menu_product_wine_aging_history"
parent="menu_wine_aging_history" action="act_product_wine_aging_history"/>
<record model="ir.ui.menu-res.group" id="menu_product_wine_aging_history_group_agronomics">
<field name="menu" ref="menu_product_wine_aging_history"/>
<field name="group" ref="group_agronomics"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_product_wine_aging_history_group_agronomics_admin">
<field name="menu" ref="menu_product_wine_aging_history"/>
<field name="group" ref="group_agronomics_admin"/>
</record>
</data>
</tryton>

View File

@ -23,7 +23,7 @@ class Location(metaclass=PoolMeta):
'invisible': Eval('type') != 'storage',
},
depends=['type'])
material = fields.Selection('get_materials', "Material",
material = fields.Many2One('stock.location.material', "Material",
states=tank_states, depends=tank_depends)
uom = fields.Many2One('product.uom', 'Uom',
states=tank_states, depends=tank_depends)
@ -34,15 +34,6 @@ class Location(metaclass=PoolMeta):
digits=(16, Eval('unit_digits', 2)),
states=tank_states, depends=['unit_digits', 'tank'])
@classmethod
def get_materials(field_name):
pool = Pool()
LocationMaterial = pool.get('stock.location.material')
materials = [(None, "")]
for material in LocationMaterial.search([]):
materials.append((material.name, material.name))
return materials
@fields.depends('uom')
def on_change_with_unit_digits(self, name=None):
if self.uom:

View File

@ -42,6 +42,9 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.message" id="msg_cant_active_contract">
<field name="text">The contract "%(contract)s" cant be activated because the parcel "%(parcel)s" is in another active contract.</field>
</record>
<record model="ir.message" id="msg_transfer_wine_aging_inputs">
<field name="text">Production "%(production)s" has more than input mark transfer wine aging. Production will be done but no history will be created.</field>
</record>
<record model="ir.message" id="msg_location_no_configured">
<field name="text">The weighing center "%(center)s" dont have a to location configured.</field>
</record>

View File

@ -25,6 +25,7 @@ class Move(metaclass=PoolMeta):
with Transaction().set_context(context):
location_quantity = sum(Product.products_by_location(
[move.to_location.id]).values())
if (location_quantity > move.to_location.max_capacity):
if (move.to_location.max_capacity
and (location_quantity > move.to_location.max_capacity)):
raise UserError(gettext(
'agronomics.msg_move_amount_exceed'))

View File

@ -2,6 +2,9 @@
# this repository contains the full copyright notices and license terms.
from decimal import Decimal
from datetime import datetime
from sql.operators import (Less, Greater, LessEqual,
GreaterEqual, Equal, NotEqual)
from sql import Null
from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
@ -116,17 +119,30 @@ class Product(WineMixin, metaclass=PoolMeta):
quality_tests = fields.One2Many('quality.test', 'document', 'Quality Tests')
quality_samples = fields.Many2Many('product.product-quality.sample',
'product', 'sample', 'Quality Samples')
wine_aging = fields.One2Many('product.wine.wine_aging.history', 'product',
"Wine Aging", readonly=True,
context={
'product': Eval('id'),
}, depends=['id'])
wine_history_material = fields.Function(fields.Text("History Material"),
'get_wine_history', searcher='search_wine_history')
wine_history_duration = fields.Function(fields.Text("History Duration"),
'get_wine_history', searcher='search_wine_history')
@classmethod
def deactivate_no_stock_variants_cron(cls):
pool = Pool()
Location = pool.get('stock.location')
ProductConfiguration = pool.get('product.configuration')
WineAgingHistory = pool.get('wine.wine_aging.history')
Date = pool.get('ir.date')
today = Date.today()
config = ProductConfiguration(1)
locations = Location.search(['type', '=', 'warehouse'])
locations = [location.id for location in locations]
with Transaction().set_context(locations=locations, with_childs=True):
if config.variant_deactivation_time:
if config.variant_deactivation_time is not None:
products = cls.search(
[
('quantity', '=', 0),
@ -134,9 +150,14 @@ class Product(WineMixin, metaclass=PoolMeta):
('create_date', '<',
(datetime.now() - config.variant_deactivation_time))
])
for product in products:
product.active = False
cls.save(products)
if products:
cls.write(products, {'active': False})
histories = WineAgingHistory.search([
('product', 'in', products),
('date_end', '=', None),
])
if histories:
WineAgingHistory.write(histories, {'date_end': today})
@classmethod
def validate(cls, products):
@ -158,6 +179,52 @@ class Product(WineMixin, metaclass=PoolMeta):
/ 100).quantize(
Decimal(str(10 ** -self.__class__.alcohol_volume.digits[1])))
def get_wine_history(self, name):
# not implemented
pass
@classmethod
def search_wine_history(cls, name, clause):
pool = Pool()
WineAgingHistory = pool.get('wine.wine_aging.history')
Material = pool.get('stock.location.material')
wineaginghistory = WineAgingHistory.__table__()
material = Material.__table__()
Operator = fields.SQL_OPERATORS[clause[1]]
value = clause[2]
join1 = wineaginghistory.join(material,
condition=wineaginghistory.material == material.id)
query = join1.select(wineaginghistory.product)
if name == 'wine_history_material':
query.where = (Operator(material.name, clause[2])
& (wineaginghistory.material != Null)
& (wineaginghistory.duration != Null))
elif name == 'wine_history_duration':
operator = clause[1]
try:
value = int(value)
except ValueError:
value = None
if value:
if operator == '=':
query.where = Equal(wineaginghistory.duration, value)
elif operator == '!=':
query.where = NotEqual(wineaginghistory.duration, value)
elif operator == '>':
query.where = Greater(wineaginghistory.duration, value)
elif operator == '>=':
query.where = GreaterEqual(wineaginghistory.duration, value)
elif operator == '<':
query.where = Less(wineaginghistory.duration, value)
elif operator == '<=':
query.where = LessEqual(wineaginghistory.duration, value)
return [('id', 'in', query)]
class Cron(metaclass=PoolMeta):
__name__ = 'ir.cron'

View File

@ -4,7 +4,7 @@ from decimal import Decimal
from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval, Bool, If
from trytond.exceptions import UserError
from trytond.exceptions import UserWarning, UserError
from trytond.i18n import gettext
from trytond.transaction import Transaction
from trytond.wizard import Wizard, StateView, StateAction, Button
@ -45,6 +45,7 @@ class ProductionTemplate(ModelSQL, ModelView):
cost_distribution_templates = fields.One2Many(
'production.cost_price.distribution.template',
'production_template', "Cost Distribution Templates")
transfer_wine_aging = fields.Boolean("Transfer Wine Aging")
inputs_products = fields.Function(fields.One2Many('product.product', None,
'Products'), 'get_products', searcher='search_input_products')
@ -470,13 +471,60 @@ class Production(metaclass=PoolMeta):
product.varieties = varieties.values()
return product
def create_wine_aged_history(self, input, outputs):
pool = Pool()
WineAgingHistory = pool.get('wine.wine_aging.history')
effective_date = input.production_input.effective_date
histories = WineAgingHistory.search([
('product', '=', input.product),
('date_end', '=', None),
])
if histories:
to_write = []
for history in histories:
to_write.extend(([history], {
'date_end': effective_date,
'duration': (effective_date - history.date_start).days,
}))
WineAgingHistory.write(*to_write)
new_histories = []
for output in outputs:
new_histories += WineAgingHistory.create([{
'production': output.production_output,
'location': output.to_location,
'material': output.to_location.material,
'product': output.product,
'date_start': effective_date,
'date_end': None
}])
if histories:
new_histories += WineAgingHistory.copy(histories, {
'production': output.production_output,
'product': output.product,
})
return new_histories
@classmethod
def done(cls, productions):
Move = Pool().get('stock.move')
pool = Pool()
Move = pool.get('stock.move')
Warning = pool.get('res.user.warning')
for production in productions:
if production.production_template.transfer_wine_aging:
if len(production.inputs) > 1:
warning_name = 'transfer_wine_aging_input_%s' % production.id
if Warning.check(warning_name):
raise UserWarning(warning_name,
gettext('agronomics.msg_transfer_wine_aging_inputs',
production=production.rec_name))
moves = []
for production in productions:
for distrib in production.output_distribution:
if distrib.distribution_state == 'draft':
if distrib.distribution_state == 'draft' and distrib.location:
product = production.create_variant(distrib.product,
production.production_template.pass_feature)
product = production.pass_feature(product)
@ -492,10 +540,17 @@ class Production(metaclass=PoolMeta):
Move.save(moves)
super().done(productions)
for production in productions:
for output in production.outputs:
production.copy_quality(output.product)
production.copy_quality_samples(output.product)
if production.production_template.transfer_wine_aging:
inputs = production.inputs
if len(inputs) == 1:
input, = inputs
outputs = production.outputs
production.create_wine_aged_history(input, outputs)
@classmethod
def set_cost(cls, productions):

View File

@ -25,3 +25,4 @@ xml:
quality.xml
contract.xml
invoice.xml
history.xml

View File

@ -182,5 +182,8 @@
<separator name="wine_quality_comment" colspan="6"/>
<field name="wine_quality_comment" widget="richtext" toolbar="0" yexpand="1" yfill="1" colspan="6"/>
</page>
<page string="Wine Aging" id="wine-aging" col="6">
<field name="wine_aging" colspan="6"/>
</page>
</xpath>
</data>

View File

@ -124,5 +124,8 @@
<field name="wine_tasting_phase_comment" tree_invisible="1"/>
<field name="wine_tasting_phase_confirm" tree_invisible="1"/>
<field name="wine_tasting_phase_success" tree_invisible="1"/>
<field name="wine_history_material" tree_invisible="1"/>
<field name="wine_history_duration" tree_invisible="1"/>
</xpath>
</data>

View File

@ -0,0 +1,8 @@
<form>
<label name="product"/>
<field name="product"/>
<label name="material"/>
<field name="material"/>
<label name="duration"/>
<field name="duration"/>
</form>

View File

@ -0,0 +1,5 @@
<tree>
<field name="product"/>
<field name="material"/>
<field name="duration"/>
</tree>

View File

@ -23,6 +23,9 @@ this repository contains the full copyright notices and license terms. -->
<newline/>
<label name="pass_certification"/>
<field name="pass_certification"/>
<newline/>
<label name="transfer_wine_aging"/>
<field name="transfer_wine_aging"/>
</group>
<field name="inputs" colspan="2"/>
<field name="outputs" colspan="2"/>

View File

@ -14,5 +14,4 @@ this repository contains the full copyright notices and license terms. -->
<label name="variant_deactivate_stock_zero"/>
<field name="variant_deactivate_stock_zero"/>
</xpath>
</data>

View File

@ -0,0 +1,16 @@
<form>
<label name="production"/>
<field name="production"/>
<label name="location"/>
<field name="location"/>
<label name="product"/>
<field name="product"/>
<label name="material"/>
<field name="material"/>
<label name="date_start"/>
<field name="date_start"/>
<label name="date_end"/>
<field name="date_end"/>
<label name="duration"/>
<field name="duration"/>
</form>

View File

@ -0,0 +1,9 @@
<tree>
<field name="production"/>
<field name="location"/>
<field name="product"/>
<field name="material"/>
<field name="date_start"/>
<field name="date_end"/>
<field name="duration"/>
</tree>