From 2faa0cb18bbde3f037f1bbcd33546640c6d3e865 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 21 Jan 2021 17:41:13 -0600 Subject: [PATCH] Add support for importing full size, uom data from CORE also add core-specific product handler and mixin, to find all UOM abbreviations in the wild. --- rattail_corepos/importing/corepos/api.py | 21 +++++++-- rattail_corepos/importing/corepos/db.py | 19 ++++++-- rattail_corepos/importing/model.py | 58 +++++++++++++++++++++++- rattail_corepos/products.py | 57 +++++++++++++++++++++++ 4 files changed, 147 insertions(+), 8 deletions(-) create mode 100644 rattail_corepos/products.py diff --git a/rattail_corepos/importing/corepos/api.py b/rattail_corepos/importing/corepos/api.py index 20f67b5..a87bcc2 100644 --- a/rattail_corepos/importing/corepos/api.py +++ b/rattail_corepos/importing/corepos/api.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2020 Lance Edgar +# Copyright © 2010-2021 Lance Edgar # # This file is part of Rattail. # @@ -441,6 +441,9 @@ class ProductImporter(FromCOREPOSAPI, corepos_importing.model.ProductImporter): 'upc', 'brand_name', 'description', + 'unit_size', + 'unit_of_measure', + 'uom_abbreviation', 'size', 'weighed', 'department_number', @@ -455,7 +458,7 @@ class ProductImporter(FromCOREPOSAPI, corepos_importing.model.ProductImporter): def setup(self): super(ProductImporter, self).setup() - model = self.config.get_model() + model = self.model query = self.session.query(model.Product)\ .join(model.CoreProduct)\ @@ -519,14 +522,13 @@ class ProductImporter(FromCOREPOSAPI, corepos_importing.model.ProductImporter): if product.get('normal_price') is not None: price = decimal.Decimal(product['normal_price']) - return { + data = { 'uuid': self.identify_product_uuid(product), 'corepos_id': int(product['id']), 'item_id': product['upc'], 'upc': upc, 'brand_name': product.get('brand') or None, 'description': product.get('description') or '', - 'size': product.get('size', '').strip() or None, 'department_number': department_number, 'subdepartment_number': subdepartment_number, @@ -541,6 +543,17 @@ class ProductImporter(FromCOREPOSAPI, corepos_importing.model.ProductImporter): 'regular_price_type': self.enum.PRICE_TYPE_REGULAR if price is not None else None, } + if self.fields_active(self.size_fields): + size_info = self.normalize_size_info(product) + data.update({ + 'size': size_info['size'], + 'unit_size': size_info['unit_size'], + 'unit_of_measure': size_info['uom_code'], + 'uom_abbreviation': size_info['uom_abbrev'], + }) + + return data + class ProductMovementImporter(FromCOREPOSAPI, corepos_importing.model.ProductImporter): """ diff --git a/rattail_corepos/importing/corepos/db.py b/rattail_corepos/importing/corepos/db.py index a582bc4..b66b43b 100644 --- a/rattail_corepos/importing/corepos/db.py +++ b/rattail_corepos/importing/corepos/db.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2020 Lance Edgar +# Copyright © 2010-2021 Lance Edgar # # This file is part of Rattail. # @@ -170,6 +170,9 @@ class ProductImporter(FromCOREPOS, corepos_importing.model.ProductImporter): 'brand_name', 'description', 'size', + 'unit_size', + 'unit_of_measure', + 'uom_abbreviation', 'weighed', 'department_number', 'subdepartment_number', @@ -194,13 +197,12 @@ class ProductImporter(FromCOREPOS, corepos_importing.model.ProductImporter): if product.normal_price is not None: price = decimal.Decimal('{:03f}'.format(product.normal_price)) - return { + data = { 'corepos_id': product.id, 'item_id': product.upc, 'upc': upc, 'brand_name': (product.brand or '').strip() or None, 'description': (product.description or '').strip(), - 'size': (product.size or '').strip() or None, 'department_number': product.department_number or None, 'subdepartment_number': product.subdepartment_number or None, @@ -213,3 +215,14 @@ class ProductImporter(FromCOREPOS, corepos_importing.model.ProductImporter): 'regular_price_multiple': 1 if price is not None else None, 'regular_price_type': self.enum.PRICE_TYPE_REGULAR if price is not None else None, } + + if self.fields_active(self.size_fields): + size_info = self.normalize_size_info(product) + data.update({ + 'size': size_info['size'], + 'unit_size': size_info['unit_size'], + 'unit_of_measure': size_info['uom_code'], + 'uom_abbreviation': size_info['uom_abbrev'], + }) + + return data diff --git a/rattail_corepos/importing/model.py b/rattail_corepos/importing/model.py index 1b8ea03..2f31ba0 100644 --- a/rattail_corepos/importing/model.py +++ b/rattail_corepos/importing/model.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2020 Lance Edgar +# Copyright © 2010-2021 Lance Edgar # # This file is part of Rattail. # @@ -24,7 +24,10 @@ Rattail model importer extensions, for CORE-POS integration """ +import decimal + from rattail import importing +from rattail.util import pretty_quantity ############################## @@ -97,6 +100,14 @@ class ProductImporter(importing.model.ProductImporter): 'corepos_id', ] + def setup(self): + super(ProductImporter, self).setup() + + if self.fields_active(self.size_fields): + app = self.config.get_app() + handler = app.get_products_handler() + self.uoms = handler.get_uom_sil_codes(self.session, uppercase=True) + def cache_query(self): query = super(ProductImporter, self).cache_query() model = self.config.get_model() @@ -108,6 +119,51 @@ class ProductImporter(importing.model.ProductImporter): return query + def get_uom_code(self, uom): + if hasattr(self, 'uoms'): + return self.uoms.get(uom.upper()) + + app = self.config.get_app() + handler = app.get_products_handler() + return handler.get_uom_sil_code(self.session, uom.upper()) + + def normalize_size_info(self, core_product): + + # convert product to dict if needed + if isinstance(core_product, dict): + core_data = core_product + else: + core_data = { + 'size': core_product.size, + 'unitofmeasure': core_product.unit_of_measure, + } + + unit_size = None + if 'size' in core_data and core_data['size'] is not None: + unit_size = decimal.Decimal(core_data['size']) + + uom_abbrev = core_data.get('unitofmeasure') + + uom_code = self.enum.UNIT_OF_MEASURE_NONE + if uom_abbrev is not None: + uom_code = self.get_uom_code(uom_abbrev) or self.enum.UNIT_OF_MEASURE_NONE + + if unit_size is not None and uom_abbrev is not None: + size = "{} {}".format(pretty_quantity(unit_size), uom_abbrev) + elif unit_size is not None: + size = pretty_quantity(unit_size) + elif uom_abbrev is not None: + size = uom_abbrev + else: + size = None + + return { + 'size': size, + 'unit_size': unit_size, + 'uom_abbrev': uom_abbrev, + 'uom_code': uom_code, + } + class ProductCostImporter(importing.model.ProductCostImporter): diff --git a/rattail_corepos/products.py b/rattail_corepos/products.py new file mode 100644 index 0000000..de67ca3 --- /dev/null +++ b/rattail_corepos/products.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2021 Lance Edgar +# +# This file is part of Rattail. +# +# Rattail is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# Rattail. If not, see . +# +################################################################################ +""" +Products Handler +""" + +from corepos.db.office_op import Session as CoreSession + +from rattail import products as base + + +class CoreProductsHandlerMixin(object): + """ + Products handler mixin for CORE-POS integration + """ + + def find_wild_uoms_in_corepos(self, session, **kwargs): + core_session = CoreSession() + + wild_uoms = core_session.execute(""" + SELECT DISTINCT UPPER(TRIM(unitofmeasure)) + FROM products + WHERE unitofmeasure IS NOT NULL AND TRIM(unitofmeasure) != '' + ORDER BY 1 + """).fetchall() + + core_session.close() + return [row[0] for row in wild_uoms] + + +class CoreProductsHandler(base.ProductsHandler, CoreProductsHandlerMixin): + """ + Custom products handler for use with CORE-POS. + """ + + def find_wild_uoms(self, session, **kwargs): + return self.find_wild_uoms_in_corepos(session, **kwargs)