From 503d1ba111c6531fbb1598d979ea1a588a0fb32a Mon Sep 17 00:00:00 2001 From: Raimon Esteve Date: Tue, 28 Mar 2023 17:13:43 +0200 Subject: [PATCH] Add wine aging history 046447 --- __init__.py | 3 + history.py | 72 ++++++++++++++++ history.xml | 100 +++++++++++++++++++++++ location.py | 11 +-- message.xml | 3 + move.py | 3 +- product.py | 75 ++++++++++++++++- production.py | 61 +++++++++++++- tryton.cfg | 1 + view/product_form.xml | 3 + view/product_list.xml | 3 + view/product_wine_aging_history_form.xml | 8 ++ view/product_wine_aging_history_list.xml | 5 ++ view/production_template_form.xml | 3 + view/template_form.xml | 1 - view/wine_aging_history_form.xml | 16 ++++ view/wine_aging_history_list.xml | 9 ++ 17 files changed, 358 insertions(+), 19 deletions(-) create mode 100644 history.py create mode 100644 history.xml create mode 100644 view/product_wine_aging_history_form.xml create mode 100644 view/product_wine_aging_history_list.xml create mode 100644 view/wine_aging_history_form.xml create mode 100644 view/wine_aging_history_list.xml diff --git a/__init__.py b/__init__.py index 74db69d..1a52ad9 100644 --- a/__init__.py +++ b/__init__.py @@ -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, diff --git a/history.py b/history.py new file mode 100644 index 0000000..ddf2de8 --- /dev/null +++ b/history.py @@ -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 diff --git a/history.xml b/history.xml new file mode 100644 index 0000000..61122d8 --- /dev/null +++ b/history.xml @@ -0,0 +1,100 @@ + + + + + + + wine.wine_aging.history + form + wine_aging_history_form + + + wine.wine_aging.history + tree + wine_aging_history_list + + + + Wine Aging History + wine.wine_aging.history + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + product.wine.wine_aging.history + form + product_wine_aging_history_form + + + product.wine.wine_aging.history + tree + product_wine_aging_history_list + + + + Wine Aging History + product.wine.wine_aging.history + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/location.py b/location.py index 62ea0e4..2a61ed6 100644 --- a/location.py +++ b/location.py @@ -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: diff --git a/message.xml b/message.xml index 2a70203..46a2cf7 100644 --- a/message.xml +++ b/message.xml @@ -42,6 +42,9 @@ this repository contains the full copyright notices and license terms. --> The contract "%(contract)s" cant be activated because the parcel "%(parcel)s" is in another active contract. + + Production "%(production)s" has more than input mark transfer wine aging. Production will be done but no history will be created. + The weighing center "%(center)s" dont have a to location configured. diff --git a/move.py b/move.py index 693e168..cb967bc 100644 --- a/move.py +++ b/move.py @@ -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')) diff --git a/product.py b/product.py index ceee305..1674e64 100644 --- a/product.py +++ b/product.py @@ -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' diff --git a/production.py b/production.py index 7a7b4cd..2d200a6 100644 --- a/production.py +++ b/production.py @@ -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): diff --git a/tryton.cfg b/tryton.cfg index b90e5b9..a6559ad 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -25,3 +25,4 @@ xml: quality.xml contract.xml invoice.xml + history.xml diff --git a/view/product_form.xml b/view/product_form.xml index a4653e5..116a19e 100644 --- a/view/product_form.xml +++ b/view/product_form.xml @@ -182,5 +182,8 @@ + + + diff --git a/view/product_list.xml b/view/product_list.xml index 97e60c7..31000ae 100644 --- a/view/product_list.xml +++ b/view/product_list.xml @@ -124,5 +124,8 @@ + + + diff --git a/view/product_wine_aging_history_form.xml b/view/product_wine_aging_history_form.xml new file mode 100644 index 0000000..77c849b --- /dev/null +++ b/view/product_wine_aging_history_form.xml @@ -0,0 +1,8 @@ +
+