diff --git a/modules/purchase_trade/company_defaults.py b/modules/purchase_trade/company_defaults.py index f32fcfd..3d8f6d5 100644 --- a/modules/purchase_trade/company_defaults.py +++ b/modules/purchase_trade/company_defaults.py @@ -1,5 +1,6 @@ from trytond.pool import Pool from trytond.transaction import Transaction +import datetime ITSA_COMPANY_NAME = 'ITSA' @@ -48,3 +49,79 @@ def default_itsa_unit(): [('name', '=', 'MT')], ]) return record_id(unit) if unit else None + + +def _company_from_values(values): + company = values.get('company') + if company and hasattr(company, 'party'): + return company + company_id = record_id(company) or Transaction().context.get('company') + if not company_id: + return None + return Pool().get('company.company')(company_id) + + +def _dimension(name): + return _search_first('analytic.dimension', [ + [('code', '=', name)], + [('name', '=', name)], + [('code', 'ilike', '%%%s%%' % name)], + [('name', 'ilike', '%%%s%%' % name)], + ]) + + +def _book_year_suffix(today=None): + if today is None: + today = Pool().get('ir.date').today() + year = today.year + if today < datetime.date(year, 4, 1): + year -= 1 + return '%02d' % (year % 100) + + +def _book_value(dimension, today=None): + if not dimension: + return None + suffix = _book_year_suffix(today) + return _search_first('analytic.dimension.value', [ + [ + ('dimension', '=', record_id(dimension)), + ('code', 'ilike', '%%%s%%' % suffix), + ], + [ + ('dimension', '=', record_id(dimension)), + ('name', 'ilike', '%%%s%%' % suffix), + ], + ]) + + +def default_itsa_analytic_dimension_assignments(company=None, today=None): + if not is_itsa_company(company): + return [] + + assignments = [] + book = _dimension('Book') + if book: + value = _book_value(book, today=today) + assignments.append({ + 'dimension': record_id(book), + 'value': record_id(value) if value else None, + }) + + strategy = _dimension('Strategy') + if strategy: + assignments.append({ + 'dimension': record_id(strategy), + 'value': None, + }) + + return assignments + + +def add_itsa_analytic_dimension_defaults(values): + if values.get('analytic_dimensions'): + return + company = _company_from_values(values) + assignments = default_itsa_analytic_dimension_assignments(company=company) + if assignments: + values['analytic_dimensions'] = [('create', assignments)] diff --git a/modules/purchase_trade/dimension.py b/modules/purchase_trade/dimension.py index ab3745c..da1c68a 100644 --- a/modules/purchase_trade/dimension.py +++ b/modules/purchase_trade/dimension.py @@ -65,7 +65,6 @@ class AnalyticDimensionAssignment(ModelSQL, ModelView): value = fields.Many2One( 'analytic.dimension.value', 'Value', - required=True, domain=[ ('dimension', '=', Eval('dimension')), ], @@ -76,4 +75,4 @@ class AnalyticDimensionAssignment(ModelSQL, ModelView): 'purchase.purchase', 'Purchase', ondelete='CASCADE' - ) \ No newline at end of file + ) diff --git a/modules/purchase_trade/lot.py b/modules/purchase_trade/lot.py index c6c898c..1de807e 100755 --- a/modules/purchase_trade/lot.py +++ b/modules/purchase_trade/lot.py @@ -1253,21 +1253,75 @@ class LotQt( ('delivered', 'Delivered') ], 'Where') lot_visible = fields.Boolean("Visible") - lot_shipment_origin = fields.Function( - fields.Reference( - selection=[ - ("stock.shipment.in", "In"), - ("stock.shipment.out", "Out"), + lot_shipment_origin = fields.Function( + fields.Reference( + selection=[ + ("stock.shipment.in", "In"), + ("stock.shipment.out", "Out"), ("stock.shipment.internal", "Internal"), ], string="Shipment", - ), - "get_shipment_origin", - ) - - def get_shipment_origin(self, name): - if self.lot_shipment_in: - return 'stock.shipment.in,' + str(self.lot_shipment_in.id) + ), + "get_shipment_origin", + ) + purchase = fields.Function( + fields.Many2One('purchase.purchase', "Purchase"), 'get_purchase') + sale = fields.Function( + fields.Many2One('sale.sale', "Sale"), 'get_sale') + purchase_reference = fields.Function( + fields.Char("Purchase Ref."), 'get_purchase_reference') + sale_reference = fields.Function( + fields.Char("Sale Ref."), 'get_sale_reference') + purchase_line_price = fields.Function( + fields.Numeric("Purchase Price", digits=(16, 4)), + 'get_purchase_line_price') + sale_line_price = fields.Function( + fields.Numeric("Sale Price", digits=(16, 4)), + 'get_sale_line_price') + + def _purchase_line(self): + return getattr(getattr(self, 'lot_p', None), 'line', None) + + def _sale_line(self): + return getattr(getattr(self, 'lot_s', None), 'sale_line', None) + + @staticmethod + def _contract_reference(contract): + if not contract: + return None + return ( + getattr(contract, 'reference', None) + or getattr(contract, 'our_reference', None)) + + def get_purchase(self, name): + line = self._purchase_line() + purchase = getattr(line, 'purchase', None) + return purchase.id if purchase else None + + def get_sale(self, name): + line = self._sale_line() + sale = getattr(line, 'sale', None) + return sale.id if sale else None + + def get_purchase_reference(self, name): + line = self._purchase_line() + return self._contract_reference(getattr(line, 'purchase', None)) + + def get_sale_reference(self, name): + line = self._sale_line() + return self._contract_reference(getattr(line, 'sale', None)) + + def get_purchase_line_price(self, name): + line = self._purchase_line() + return getattr(line, 'unit_price', None) + + def get_sale_line_price(self, name): + line = self._sale_line() + return getattr(line, 'unit_price', None) + + def get_shipment_origin(self, name): + if self.lot_shipment_in: + return 'stock.shipment.in,' + str(self.lot_shipment_in.id) elif self.lot_shipment_out: return 'stock.shipment.out,' + str(self.lot_shipment_out.id) elif self.lot_shipment_internal: @@ -2725,6 +2779,16 @@ class LotReport( r_lot_unit_line = fields.Function(fields.Many2One('product.uom', "Unit"),'get_unit') r_lot_price = fields.Function(fields.Numeric("Price", digits='r_lot_unit_line'),'get_lot_price') r_lot_price_sale = fields.Function(fields.Numeric("Price", digits='r_lot_unit_line'),'get_lot_sale_price') + r_purchase_reference = fields.Function( + fields.Char("Purchase Ref."), 'get_purchase_reference') + r_sale_reference = fields.Function( + fields.Char("Sale Ref."), 'get_sale_reference') + r_purchase_line_price = fields.Function( + fields.Numeric("Purchase Price", digits=(16, 4)), + 'get_purchase_line_price') + r_sale_line_price = fields.Function( + fields.Numeric("Sale Price", digits=(16, 4)), + 'get_sale_line_price') r_del_period = fields.Many2One( 'product.month', "Purchase Shipment Period") r_sale_del_period = fields.Many2One( @@ -2941,17 +3005,37 @@ class LotReport( price = premium + l.unit_price return round(price,2) - def get_lot_sale_price(self,name=None): - price = Decimal(0) - # if self.sale_line: - # l = Pool().get('sale.line')(self.sale_line) + def get_lot_sale_price(self,name=None): + price = Decimal(0) + # if self.sale_line: + # l = Pool().get('sale.line')(self.sale_line) # premium = Decimal(0) # if self.lot_premium_sale: # premium = self.lot_premium_sale - # price = premium + l.unit_price - return round(price,2) - - #@classmethod + # price = premium + l.unit_price + return round(price,2) + + @staticmethod + def _contract_reference(contract): + if not contract: + return None + return ( + getattr(contract, 'reference', None) + or getattr(contract, 'our_reference', None)) + + def get_purchase_reference(self, name=None): + return self._contract_reference(self.r_purchase) + + def get_sale_reference(self, name=None): + return self._contract_reference(self.r_sale) + + def get_purchase_line_price(self, name=None): + return getattr(self.r_line, 'unit_price', None) + + def get_sale_line_price(self, name=None): + return getattr(self.r_sale_line, 'unit_price', None) + + #@classmethod #def delete(cls, records): # Unmatch = Pool().get('lot.unmatch', type='wizard') # session_id, _, _ = Unmatch.create() @@ -3853,8 +3937,8 @@ class LotMatchingStart(ModelView): val = {} val['lot_id'] = r['r_lot_s'] val['lot_r_id'] = r['id'] - 10000000 - val['lot_sale'] = r['r_sale'] - val['lot_purchase'] = r['r_purchase'] + val['lot_sale'] = r['r_sale'] + val['lot_purchase'] = r['r_purchase'] val['lot_shipment_in'] = r['r_lot_shipment_in'] val['lot_shipment_internal'] = r['r_lot_shipment_internal'] val['lot_shipment_out'] = r['r_lot_shipment_out'] @@ -3866,10 +3950,15 @@ class LotMatchingStart(ModelView): val['lot_matched_qt'] = 0 val['lot_cp'] = r['r_client'] lqt = LotQt(r['id'] - 10000000) + line = LotQt._matching_line(lqt, 'sale') + sale = getattr(line, 'sale', None) + val['lot_sale_line_price'] = getattr(line, 'unit_price', None) + val['lot_sale_reference'] = ( + getattr(sale, 'reference', None) + or getattr(sale, 'our_reference', None)) val['lot_already_matched_qt'] = ( LotQt._matching_already_matched(lqt, 'sale')) - val.update(LotQt._matching_line_tolerance_info( - LotQt._matching_line(lqt, 'sale'))) + val.update(LotQt._matching_line_tolerance_info(line)) lp.append(val) return lp @@ -3896,10 +3985,15 @@ class LotMatchingStart(ModelView): val['lot_matched_qt'] = 0 val['lot_cp'] = r['r_supplier'] lqt = LotQt(r['id'] - 10000000) + line = LotQt._matching_line(lqt, 'purchase') + purchase = getattr(line, 'purchase', None) + val['lot_purchase_line_price'] = getattr(line, 'unit_price', None) + val['lot_purchase_reference'] = ( + getattr(purchase, 'reference', None) + or getattr(purchase, 'our_reference', None)) val['lot_already_matched_qt'] = ( LotQt._matching_already_matched(lqt, 'purchase')) - val.update(LotQt._matching_line_tolerance_info( - LotQt._matching_line(lqt, 'purchase'))) + val.update(LotQt._matching_line_tolerance_info(line)) lp.append(val) return lp @@ -3910,9 +4004,15 @@ class LotMatchingLot(ModelView): lms = fields.Many2One('lot.matching.start',"Matching") lot_id = fields.Many2One('lot.lot',"Lot") lot_r_id = fields.Many2One('lot.qt',"Lot Qt") - lot_purchase = fields.Many2One('purchase.purchase', "Purchase") - lot_sale = fields.Many2One('sale.sale', "Sale") - lot_name = fields.Char("Name",readonly=True) + lot_purchase = fields.Many2One('purchase.purchase', "Purchase") + lot_sale = fields.Many2One('sale.sale', "Sale") + lot_purchase_reference = fields.Char("Purchase Ref.", readonly=True) + lot_sale_reference = fields.Char("Sale Ref.", readonly=True) + lot_purchase_line_price = fields.Numeric( + "Purchase Price", digits=(16, 4), readonly=True) + lot_sale_line_price = fields.Numeric( + "Sale Price", digits=(16, 4), readonly=True) + lot_name = fields.Char("Name",readonly=True) lot_type = fields.Selection([ ('virtual', 'Open'), ('physic', 'Physic'), diff --git a/modules/purchase_trade/purchase.py b/modules/purchase_trade/purchase.py index 95dbf70..10a5abc 100755 --- a/modules/purchase_trade/purchase.py +++ b/modules/purchase_trade/purchase.py @@ -23,10 +23,12 @@ import json import re import jwt from collections import defaultdict -from trytond.exceptions import UserWarning, UserError +from trytond.exceptions import UserWarning, UserError from trytond.modules.purchase_trade.numbers_to_words import quantity_to_words, amount_to_currency_words, format_date_en from trytond.modules.purchase_trade.company_defaults import ( - default_itsa_currency, default_itsa_unit, is_itsa_company, record_id) + add_itsa_analytic_dimension_defaults, + default_itsa_analytic_dimension_assignments, default_itsa_currency, + default_itsa_unit, is_itsa_company, record_id) import requests import io @@ -293,6 +295,18 @@ class Purchase(metaclass=PoolMeta): if purchases: super().process(purchases) + @classmethod + def create(cls, vlist): + for values in vlist: + add_itsa_analytic_dimension_defaults(values) + return super().create(vlist) + + @classmethod + def default_analytic_dimensions(cls): + default = getattr(super(), 'default_analytic_dimensions', None) + values = default() if default else [] + return values or default_itsa_analytic_dimension_assignments() + btb = fields.Many2One('back.to.back',"Back to back") charter_conditions = fields.One2Many( 'charter.condition', 'purchase', "Charter Conditions") diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py index 2e4189f..ec440af 100755 --- a/modules/purchase_trade/sale.py +++ b/modules/purchase_trade/sale.py @@ -19,7 +19,9 @@ from collections import defaultdict from trytond.exceptions import UserWarning, UserError from trytond.modules.purchase_trade.numbers_to_words import quantity_to_words, amount_to_currency_words, format_date_en from trytond.modules.purchase_trade.company_defaults import ( - default_itsa_currency, default_itsa_unit, is_itsa_company, record_id) + add_itsa_analytic_dimension_defaults, + default_itsa_analytic_dimension_assignments, default_itsa_currency, + default_itsa_unit, is_itsa_company, record_id) logger = logging.getLogger(__name__) @@ -254,6 +256,18 @@ class Sale(metaclass=PoolMeta): if sales: super().process(sales) + @classmethod + def create(cls, vlist): + for values in vlist: + add_itsa_analytic_dimension_defaults(values) + return super().create(vlist) + + @classmethod + def default_analytic_dimensions(cls): + default = getattr(super(), 'default_analytic_dimensions', None) + values = default() if default else [] + return values or default_itsa_analytic_dimension_assignments() + btb = fields.Many2One('back.to.back',"Back to back") charter_conditions = fields.One2Many( 'charter.condition', 'sale', "Charter Conditions") diff --git a/modules/purchase_trade/stock.py b/modules/purchase_trade/stock.py index 7e144af..e7eb5bd 100755 --- a/modules/purchase_trade/stock.py +++ b/modules/purchase_trade/stock.py @@ -73,9 +73,49 @@ class Move(metaclass=PoolMeta): blnumber = fields.Char("BL number") lotqt = fields.One2Many('lot.qt','lot_move',"Lots") lot = fields.Many2One('lot.lot',"Lot") - - def get_linked_transit_move(self): - if self.from_location.is_transit(): + trade_purchase = fields.Function( + fields.Many2One('purchase.purchase', "Purchase"), 'get_trade_purchase') + trade_sale = fields.Function( + fields.Many2One('sale.sale', "Sale"), 'get_trade_sale') + trade_purchase_reference = fields.Function( + fields.Char("Purchase Ref."), 'get_trade_purchase_reference') + trade_sale_reference = fields.Function( + fields.Char("Sale Ref."), 'get_trade_sale_reference') + + def _trade_purchase_line(self): + return getattr(getattr(self, 'lot', None), 'line', None) + + def _trade_sale_line(self): + return getattr(getattr(self, 'lot', None), 'sale_line', None) + + @staticmethod + def _contract_reference(contract): + if not contract: + return None + return ( + getattr(contract, 'reference', None) + or getattr(contract, 'our_reference', None)) + + def get_trade_purchase(self, name): + line = self._trade_purchase_line() + purchase = getattr(line, 'purchase', None) + return purchase.id if purchase else None + + def get_trade_sale(self, name): + line = self._trade_sale_line() + sale = getattr(line, 'sale', None) + return sale.id if sale else None + + def get_trade_purchase_reference(self, name): + line = self._trade_purchase_line() + return self._contract_reference(getattr(line, 'purchase', None)) + + def get_trade_sale_reference(self, name): + line = self._trade_sale_line() + return self._contract_reference(getattr(line, 'sale', None)) + + def get_linked_transit_move(self): + if self.from_location.is_transit(): Move = Pool().get('stock.move') Location = Pool().get('stock.location') moves = Move.search([('lot','=',self.lot),('to_location','=',Location.get_transit_id())],order=[('id', 'DESC')],limit=1) diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index 55dfc5b..8bc6cfb 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -17,6 +17,7 @@ from trytond.modules.purchase_trade import purchase as purchase_module from trytond.modules.purchase_trade import sale as sale_module from trytond.modules.purchase_trade import stock as stock_module from trytond.modules.purchase_trade import fee as fee_module +from trytond.modules.purchase_trade import company_defaults from trytond.modules.purchase_trade.service import ContractFactory @@ -24,6 +25,15 @@ class PurchaseTradeTestCase(ModuleTestCase): 'Test purchase_trade module' module = 'purchase_trade' + def test_itsa_book_year_suffix_uses_april_fiscal_start(self): + 'ITSA book year changes on April 1st' + self.assertEqual( + company_defaults._book_year_suffix(datetime.date(2026, 3, 31)), + '25') + self.assertEqual( + company_defaults._book_year_suffix(datetime.date(2026, 4, 1)), + '26') + @with_transaction() def test_purchase_list_fields_use_first_trade_line(self): 'purchase list helper fields use the first real purchase line' diff --git a/modules/purchase_trade/view/lot_matching_lot_tree.xml b/modules/purchase_trade/view/lot_matching_lot_tree.xml index f96102e..ff5c4a2 100755 --- a/modules/purchase_trade/view/lot_matching_lot_tree.xml +++ b/modules/purchase_trade/view/lot_matching_lot_tree.xml @@ -1,6 +1,8 @@ + + diff --git a/modules/purchase_trade/view/lot_matching_lot_tree2.xml b/modules/purchase_trade/view/lot_matching_lot_tree2.xml index bf5a017..d2b0112 100755 --- a/modules/purchase_trade/view/lot_matching_lot_tree2.xml +++ b/modules/purchase_trade/view/lot_matching_lot_tree2.xml @@ -1,6 +1,8 @@ + + diff --git a/modules/purchase_trade/view/lot_qt_planned_tree.xml b/modules/purchase_trade/view/lot_qt_planned_tree.xml index b510faf..1cf142f 100644 --- a/modules/purchase_trade/view/lot_qt_planned_tree.xml +++ b/modules/purchase_trade/view/lot_qt_planned_tree.xml @@ -1,4 +1,8 @@ + + + + diff --git a/modules/purchase_trade/view/lot_report_list.xml b/modules/purchase_trade/view/lot_report_list.xml index f192b92..a595a6d 100755 --- a/modules/purchase_trade/view/lot_report_list.xml +++ b/modules/purchase_trade/view/lot_report_list.xml @@ -5,8 +5,12 @@ + + + + diff --git a/modules/purchase_trade/view/move_list_shipment_in.xml b/modules/purchase_trade/view/move_list_shipment_in.xml index da4f663..4591208 100755 --- a/modules/purchase_trade/view/move_list_shipment_in.xml +++ b/modules/purchase_trade/view/move_list_shipment_in.xml @@ -4,6 +4,10 @@ this repository contains the full copyright notices and license terms. --> + + + +