From 77fe2f0589cfffcf121c532fbc11c98be5d5e8c5 Mon Sep 17 00:00:00 2001 From: "AzureAD\\SylvainDUVERNAY" Date: Mon, 1 Jun 2026 21:43:46 +0200 Subject: [PATCH] Import Price - New Excel file structure --- modules/__pycache__/__init__.cpython-314.pyc | Bin 0 -> 22180 bytes .../__pycache__/pricing.cpython-314.pyc | Bin 0 -> 55177 bytes modules/purchase_trade/pricing.py | 91 +++++++++++++++++- .../__pycache__/test_module.cpython-314.pyc | Bin 0 -> 25694 bytes modules/purchase_trade/tests/test_module.py | 42 ++++++++ .../view/import_prices_start_form.xml | 3 + 6 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 modules/__pycache__/__init__.cpython-314.pyc create mode 100644 modules/purchase_trade/__pycache__/pricing.cpython-314.pyc create mode 100644 modules/purchase_trade/tests/__pycache__/test_module.cpython-314.pyc diff --git a/modules/__pycache__/__init__.cpython-314.pyc b/modules/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9735663b519851e251913b134b6796dc59fc9308 GIT binary patch literal 22180 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 literal 0 HcmV?d00001 diff --git a/modules/purchase_trade/__pycache__/pricing.cpython-314.pyc b/modules/purchase_trade/__pycache__/pricing.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89008ea471240a9cd4d39e6065ea265d4146d484 GIT binary patch literal 55177 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 NNa$y%l)=(9>^Eu?kbM9E literal 0 HcmV?d00001 diff --git a/modules/purchase_trade/pricing.py b/modules/purchase_trade/pricing.py index f397d3f..ee4cdfa 100755 --- a/modules/purchase_trade/pricing.py +++ b/modules/purchase_trade/pricing.py @@ -69,12 +69,20 @@ class ImportPricesStart(ModelView): "Import Prices" __name__ = 'purchase_trade.import_prices.start' + file_structure = fields.Selection([ + ('historical', "Historical Prices"), + ('forward', "Forward Prices"), + ], "Excel file structure", required=True) file_ = fields.Binary('Excel file', required=True, filename='filename') filename = fields.Char('Filename') create_missing_price_index = fields.Boolean( "Create price index if missing") overwrite_existing_price = fields.Boolean("Overwrite existing price") + @staticmethod + def default_file_structure(): + return 'historical' + class ImportPricesResult(ModelView): "Import Prices Result" @@ -112,7 +120,8 @@ class ImportPrices(Wizard): } def transition_import_(self): - rows = self._read_xlsx(self.start.file_) + rows = self._read_xlsx( + self.start.file_, file_structure=self.start.file_structure) stats = self._import_rows( rows, create_missing_price_index=( @@ -142,7 +151,8 @@ class ImportPrices(Wizard): 'errors': [], } - for row_number, row in enumerate(rows, start=2): + for index, row in enumerate(rows, start=2): + row_number = row.get('_row_number', index) price_index = (row.get('price_index') or '').strip() try: price_date = cls._as_date(row.get('price_date')) @@ -341,7 +351,7 @@ class ImportPrices(Wizard): return ', '.join(parts) or 'price imported' @classmethod - def _read_xlsx(cls, data): + def _read_xlsx(cls, data, file_structure='historical'): try: with zipfile.ZipFile(BytesIO(data)) as workbook: shared_strings = cls._read_shared_strings(workbook) @@ -355,6 +365,12 @@ class ImportPrices(Wizard): if not rows: return [] + if file_structure == 'forward': + return cls._read_forward_price_rows(rows, shared_strings, ns) + return cls._read_historical_price_rows(rows, shared_strings, ns) + + @classmethod + def _read_historical_price_rows(cls, rows, shared_strings, ns): headers = {} for cell in rows[0].findall('s:c', ns): index = cls._cell_column_index(cell.get('r')) @@ -381,6 +397,59 @@ class ImportPrices(Wizard): result.append(values) return result + @classmethod + def _read_forward_price_rows(cls, rows, shared_strings, ns): + headers = {} + month_terms = {} + for cell in rows[0].findall('s:c', ns): + index = cls._cell_column_index(cell.get('r')) + header = cls._cell_value(cell, shared_strings) + normalized = cls._normalize_header(header) + if index == 1 and normalized == 'priceindex': + headers[index] = 'price_index' + elif index == 2 and normalized == 'pricedate': + headers[index] = 'price_date' + elif index and index >= 3 and header not in (None, ''): + month_terms[index] = cls._as_month_term(header) + + missing = [] + if headers.get(1) != 'price_index': + missing.append('price_index') + if headers.get(2) != 'price_date': + missing.append('price_date') + if missing: + raise UserError( + "Missing columns in Excel file: %s" % ', '.join(missing)) + if not month_terms: + raise UserError( + "Missing month term columns in Excel file.") + + result = [] + for excel_row_number, sheet_row in enumerate(rows[1:], start=2): + row_values = {} + for cell in sheet_row.findall('s:c', ns): + index = cls._cell_column_index(cell.get('r')) + row_values[index] = cls._cell_value(cell, shared_strings) + + price_index = (row_values.get(1) or '').strip() + price_date = row_values.get(2) + if not price_index and price_date in (None, ''): + continue + + for index, month_term in sorted(month_terms.items()): + price_value = row_values.get(index) + if price_value in (None, ''): + continue + result.append({ + '_row_number': excel_row_number, + 'price_index': ( + '%s %s' % (price_index, month_term) + if price_index else ''), + 'price_date': price_date, + 'price_value': price_value, + }) + return result + @staticmethod def _read_shared_strings(workbook): try: @@ -473,8 +542,20 @@ class ImportPrices(Wizard): except ValueError: pass raise UserError("Invalid price_date: %s" % text) - -class MtmScenario(ModelSQL, ModelView): + + @classmethod + def _as_month_term(cls, value): + if isinstance(value, datetime.date): + return value.strftime('%Y-%m') + text = str(value).strip() + match = re.match(r'^(20\d{2})[-_/\. ](0[1-9]|1[0-2])$', text) + if match: + return '%s-%s' % (match.group(1), match.group(2)) + if re.match(r'^\d+(\.\d+)?$', text): + return cls._as_date(text).strftime('%Y-%m') + raise UserError("Invalid month term: %s" % text) + +class MtmScenario(ModelSQL, ModelView): "MtM Scenario" __name__ = 'mtm.scenario' diff --git a/modules/purchase_trade/tests/__pycache__/test_module.cpython-314.pyc b/modules/purchase_trade/tests/__pycache__/test_module.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6eb3aa3009bbd1886942a0ce31e32af7f0a1cff6 GIT binary patch literal 25694 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 + + + price_index + price_date + 2026-06 + 2026-07 + 2026-08 + + + CFR Chile + 2026-05-01 + 400 + 410 + 420 + + + FOB Peru + 2026-05-01 + 390 + 400 + 410 + + + + ''') + rows = sheet.findall('.//s:sheetData/s:row', ns) + + result = ImportPrices._read_forward_price_rows(rows, [], ns) + + self.assertEqual(len(result), 6) + self.assertEqual(result[0]['price_index'], 'CFR Chile 2026-06') + self.assertEqual(result[0]['price_date'], '2026-05-01') + self.assertEqual(result[0]['price_value'], '400') + self.assertEqual(result[-1]['price_index'], 'FOB Peru 2026-08') + self.assertEqual(result[-1]['_row_number'], 3) + def test_price_value_unique_rejects_duplicate_create(self): 'price value uniqueness rejects duplicate model creation' price_value = SimpleNamespace( diff --git a/modules/purchase_trade/view/import_prices_start_form.xml b/modules/purchase_trade/view/import_prices_start_form.xml index 399f7f3..17a97e8 100644 --- a/modules/purchase_trade/view/import_prices_start_form.xml +++ b/modules/purchase_trade/view/import_prices_start_form.xml @@ -1,4 +1,7 @@
+