from django.db import models from outils.common_imports import * from outils.common import OdooAPI from outils.common import CouchDB from shelfs.models import Shelfs p_fields = [ 'name', 'list_price', 'categ_id', 'uom_id', 'incoming_qty', 'qty_available', 'image_small', 'price_volume', 'price_weight_net'] max_timeslots_cart_file = 'shop/max_timeslot_carts.txt' # Shop settings set by admin user shop_admin_settings_file = 'shop/shop_admin_settings.json' # Already exists in Inventory, but since it has to be modified soon, copied here def build_tree_from_categories(elts, parent_id = False): branch = [] for e in elts: to_compare = e['parent_id'] if not type(to_compare) == bool: to_compare = to_compare[0] if to_compare == parent_id: children = build_tree_from_categories(elts, e['id']) if len(children) > 0: e['children'] = children branch.append(e) return branch def get_all_children(branch): children = [] if 'children' in branch: for c in branch['children']: children.append({'id': c['id'], 'name': c['name']}) if 'children' in c: children += get_all_children(c) return children def get_all_children_ids(branch): ids = [] for c in get_all_children(branch): ids.append(c['id']) return ids class CagetteShop(models.Model): """Class to handle cagette Shop.""" @staticmethod def get_default_shop_admin_settings(): return {'closing_dates': [], 'capital_message': '', 'max_timeslot_carts': settings.DEFAULT_MAX_TIMESLOT_CARTS} @staticmethod def filter_products_according_settings(pdts): res = pdts try: conditions = getattr(settings, 'SHOP_LIMIT_PRODUCTS', []) filtered = [] for p in pdts: keep_it = True if 'relatively_available' in conditions: if float(p['qty_available']) <= 0 and float(p['incoming_qty']) <= 0: keep_it = False if 'no_shelf' in conditions and (isinstance(p['shelf_id'], list) is False): keep_it = False # print(p['name'] + ' écarté car pas de rayon') if keep_it is True: filtered.append(p) res = filtered except Exception as e: coop_logger.error("Shop, filter_products_according_settings : %s", str(e)) return res @staticmethod def get_all_available_bought_products_by_member(member_id): res = {} try: pdts = [] limit_conditions = [] try: limit_conditions = settings.SHOP_LIMIT_PRODUCTS except: pass api = OdooAPI() params = {'member_id': member_id} odoo_result = api.execute('lacagette.pos_member_purchases', 'get_member_available_products_among_bought_pool', params) if odoo_result and 'pdts' in odoo_result: if len(odoo_result['pdts']) > 0: pids = [] for p in odoo_result['pdts']: pids.append(p['product_id']) # removed ['qty_available', '>', 0] c = [['id', 'in', pids], ['sale_ok', '=', True], ['active', '=', True]] if 'no_shelf' in limit_conditions: p_fields.append('shelf_id') res_pdts = api.search_read('product.product', c, p_fields) # construct pdts as to be ordered as odoo_result for p in odoo_result['pdts']: for rp in res_pdts: if rp['id'] == p['product_id']: rp['qty_available'] = float("{0:.3f}".format(rp['qty_available'])) pdts.append(rp) res['pdts'] = CagetteShop.filter_products_according_settings(pdts) except Exception as e: res['error'] = str(e) return res # Already exists in Inventory, but since it has to be modified soon, copied here @staticmethod def get_product_categories(): api = OdooAPI() tree = [] try: fields = ['parent_id', 'name'] res = api.search_read('product.category', [], fields) tree = build_tree_from_categories(res) except Exception as e: coop_logger.error('get_product_categories : %s', str(e)) return tree @staticmethod def get_cat_children_ids(categ_id): cat_ids = [categ_id] tree = CagetteShop.get_product_categories() branch = None for cats in tree: if 'children' in cats: for c in cats['children']: if str(c['id']) == str(categ_id): branch = c if not (branch is None): cat_ids += get_all_children_ids(branch) return cat_ids @staticmethod def get_cat_children(categ_id): children = [] tree = CagetteShop.get_product_categories() branch = None for cats in tree: if 'children' in cats: for c in cats['children']: if str(c['id']) == str(categ_id): branch = c if not (branch is None): children = get_all_children(branch) return children @staticmethod def get_categories_nb_of_products(): """Needs lacagette_categories Odoo module to be activated""" res = {} try: api = OdooAPI() res = api.execute('lacagette.categories', 'get_all_with_products_count', {}) except Exception as e: coop_logger.error('get_categories_nb_of_products %s', str(e)) res['error'] = str(e) return res @staticmethod def get_category_products(categ_id): res = {} try: pdts = [] limit_conditions = getattr(settings, 'SHOP_LIMIT_PRODUCTS', []) api = OdooAPI() cat_ids = CagetteShop.get_cat_children_ids(categ_id) # removed ['qty_available', '>', 0] c = [['categ_id.id', 'in', cat_ids], ['sale_ok', '=', True], ['active', '=', True]] if 'categ_id' in p_fields: p_fields.remove('categ_id') if 'no_shelf' in limit_conditions: p_fields.append('shelf_id') res_pdts = api.search_read('product.product', c, p_fields, order='name ASC') for rp in res_pdts: rp['qty_available'] = float("{0:.3f}".format(rp['qty_available'])) pdts.append(rp) res['pdts'] = CagetteShop.filter_products_according_settings(pdts) except Exception as e: coop_logger.error('get_category_products %s %s', categ_id, str(e)) res['error'] = str(e) return res @staticmethod def get_products_matching(kw): res = {} try: pdts = [] limit_conditions = [] try: limit_conditions = settings.SHOP_LIMIT_PRODUCTS except: pass api = OdooAPI() # removed ['qty_available', '>', 0] c = [['name', 'ilike', '%' + kw + '%'], ['sale_ok', '=', True], ['active', '=', True]] if 'categ_id' in p_fields: p_fields.remove('categ_id') if 'no_shelf' in limit_conditions: p_fields.append('shelf_id') res_pdts = api.search_read('product.product', c, p_fields, order='name ASC') for rp in res_pdts: rp['qty_available'] = float("{0:.3f}".format(rp['qty_available'])) pdts.append(rp) res['pdts'] = CagetteShop.filter_products_according_settings(pdts) except Exception as e: res['error'] = str(e) return res @staticmethod def registrerCartInitialization(cart, partner_id): result = {} try: cart['init_time'] = time.time() cart['partner_id'] = partner_id partner = {} try: api = OdooAPI() c = [['id', '=', partner_id]] f = ['display_name', 'email'] res_p = api.search_read('res.partner', c, f, 1) if (res_p): partner = res_p[0] except Exception as e: cart['error_while_saving'] = str(e) cart['partner'] = partner c_db = CouchDB(arg_db='shop') result = c_db.dbc.save(cart) except Exception as e: result['error'] = str(e) return result @staticmethod def registrerCart(cart, partner_id, mode="shop"): result = {} try: cart['submitted_time'] = time.time() cart['total'] = "{0:.2f}".format(cart['total']) partner = {} try: api = OdooAPI() c = [['id', '=', partner_id]] f = ['display_name', 'email'] res_p = api.search_read('res.partner', c, f, 1) if (res_p): partner = res_p[0] except Exception as e: cart['error_while_saving'] = str(e) cart['partner'] = partner c_db = CouchDB(arg_db='shop') result = c_db.updateDoc(cart) if result: try: from outils.mail import CagetteMail CagetteMail.sendCartValidation(partner['email'], cart, mode) except Exception as e: coop_logger.error("Shop, registrerCart : %s, %s", str(e), str(cart)) except Exception as e: result['error'] = str(e) return result @staticmethod def get_cart_products_data(cart): import re result = {} try: api = OdooAPI() pids = [] for p in cart['products']: pids.append(p['id']) c = [['id', 'in', pids]] f = ['qty_available', 'shelf_id'] res_data = api.search_read('product.product', c, f) shelfs_sortorder = Shelfs.get_shelfs_sortorder() if res_data: for line in res_data: shelf = '' if not line['shelf_id'] is False: for so_line in shelfs_sortorder: if so_line['id'] == line['shelf_id'][0]: shelf = so_line['sort_order'] if float(shelf) == float(re.sub(r'\..*', '', str(shelf))): shelf = str(int(shelf)) result[line['id']] = {'stock': line['qty_available'], 'shelf': shelf} except Exception as e: result['error'] = str(e) return result @staticmethod def orderCartProducts(cart, pdts_data): """Products will be sorted by shelf value.""" shelf_pdts = {} without_shelf_pdts = [] ordered_products = [] for p in cart['products']: if p['id'] in pdts_data: shelf = pdts_data[p['id']]['shelf'] p['shelf'] = shelf p['stock'] = "{0:.3f}".format(pdts_data[p['id']]['stock']) if len(str(shelf)) > 0: if not (shelf in shelf_pdts): shelf_pdts[shelf] = [p] else: shelf_pdts[shelf].append(p) else: without_shelf_pdts.append(p) else: p['shelf'] = p['stock'] = "???" without_shelf_pdts.append(p) s_keys = sorted(shelf_pdts.keys(), key=float) for k in s_keys: ordered_products += shelf_pdts[k] ordered_products += without_shelf_pdts cart['products'] = ordered_products return cart @staticmethod def get_promoted_and_discounted_products(): pdts = {'discounted': [], 'promoted': []} try: api = OdooAPI() shelf_ids = settings.PROMOTE_SHELFS_IDS + settings.DISCOUNT_SHELFS_IDS c = [['shelf_id', 'in', shelf_ids], ['sale_ok', '=', True], ['active', '=', True]] p_fields.append('shelf_id') if 'categ_id' in p_fields: p_fields.remove('categ_id') res_pdts = api.search_read('product.product', c, p_fields, order='name ASC') for rp in res_pdts: rp['qty_available'] = float("{0:.3f}".format(rp['qty_available'])) if rp['image_small'] is False: rp['image_small'] = '' if int(rp['shelf_id'][0]) in settings.PROMOTE_SHELFS_IDS: pdts['promoted'].append(rp) else: pdts['discounted'].append(rp) except Exception as e: pass # it doesn't matter return pdts @staticmethod def deleteCartFromCDB(cart_id): result = {} try: c_db = CouchDB(arg_db='shop') doc = c_db.getDocById(cart_id) result['del_action'] = c_db.delete(doc) except Exception as e: result['error'] = str(e) return result @staticmethod def destroy_connected_user_cart(request): import re result = {} try: # TODO : make function to clean received couchdb id cart_id = re.sub(r'[^0-9a-z]', '', request.POST.get('cart_id')) connected_mid = request.COOKIES['id'] c_db = CouchDB(arg_db='shop') cart = c_db.getDocById(cart_id) if cart and '_id' in cart: if str(connected_mid) == str(cart['partner']['id']): result = CagetteShop.deleteCartFromCDB(cart_id) else: result['error'] = "Forbidden (detroy other people cart)" else: result['error'] = "Not found" except Exception as e: result['error'] = str(e) return result @staticmethod def change_best_date(cart_id, request): result = {} try: connected_mid = request.COOKIES['id'] c_db = CouchDB(arg_db='shop') cart = c_db.getDocById(cart_id) if cart and '_id' in cart: if str(connected_mid) == str(cart['partner']['id']): cart['best_date'] = request.POST.get('new_date') result['changed'] = c_db.dbc.update([cart]) else: result['error'] = "Forbidden (detroy other people cart)" else: result['error'] = "Not found" except Exception as e: result['error'] = str(e) return result @staticmethod def addCartProductToCart(cart, added_cart): result = {} try: if cart['state'] == 'validating' or cart['state'] == 'init': total = float(cart['total']) + float(added_cart['total']) cart['total'] = "{0:.3f}".format(total) cart['products'] += added_cart['products'] cart['state'] = 'validating' c_db = CouchDB(arg_db='shop') result = c_db.dbc.update([cart]) if result and True in result[0]: result = CagetteShop.deleteCartFromCDB(added_cart['_id']) else: result['error'] = "Cart status forbidden gathering (" + cart['state'] + ")" except Exception as e: result['error'] = str(e) return result @staticmethod def fusion_carts(request): import re result = {} try: # TODO : make function to clean received couchdb id cart_id = re.sub(r'[^0-9a-z]', '', request.POST.get('id')) add_id = re.sub(r'[^0-9a-z]', '', request.POST.get('add_id')) connected_mid = request.COOKIES['id'] c_db = CouchDB(arg_db='shop') cart = c_db.getDocById(cart_id) if cart and '_id' in cart: if str(connected_mid) == str(cart['partner']['id']): added_cart = c_db.getDocById(add_id) if added_cart and '_id' in added_cart: result = CagetteShop.addCartProductToCart(cart, added_cart) else: result['error'] = "Forbidden (gather other people carts)" else: result['error'] = "Forbidden (gather other people carts)" else: result['error'] = "Not found" except Exception as e: result['error'] = str(e) return result @staticmethod def getMaxCartPerSlot(): max_timeslot_carts = int(settings.DEFAULT_MAX_TIMESLOT_CARTS) try: f = open(max_timeslots_cart_file, 'r+') max_timeslot_carts = int(f.readline()) f.close() except Exception as e: if 'No such file or directory' in str(e): f = open(max_timeslots_cart_file, 'w') f.write(str(max_timeslot_carts)) f.close() else: coop_logger.error("Shop, getMaxCartPerSlot : %s", str(e)) # if shop_settings contains an entry, it's value is returned shop_settings = CagetteShop.get_shop_settings() try: if 'max_timeslot_carts' in shop_settings: max_timeslot_carts = int(shop_settings['max_timeslot_carts']) except: pass return max_timeslot_carts @staticmethod def getSlotSize(): slot_size = settings.SHOP_SLOT_SIZE return slot_size @staticmethod def get_full_slots(): max_timeslot_carts = CagetteShop.getMaxCartPerSlot() c_db = CouchDB(arg_db='shop') all_docs = c_db.getAllDocs() # TODO use view /reduce slots = {} fulled = [] for doc in all_docs: if doc['best_date'] in slots.keys(): slots[doc['best_date']] += 1 else: slots[doc['best_date']] = 1 for k in slots: if slots[k] >= max_timeslot_carts: fulled.append(k) return fulled @staticmethod def isCartRespectingTimeSlotContraints(cart): import datetime answer = False try: received_date = datetime.datetime.strptime(cart['best_date'], '%Y-%m-%d %H:%M') if received_date.weekday() > 0 and received_date.weekday() < 6: # the received date is not a monday nor a sunday now_plus_delay = datetime.datetime.now() + datetime.timedelta(hours=settings.MIN_DELAY_FOR_SLOT + 1) d_diff = received_date - now_plus_delay answer = d_diff > datetime.timedelta(seconds=1) if answer is True: # else no need to go further verifying time slot full_slots = CagetteShop.get_full_slots() for ts in full_slots: if ts == cart['best_date']: answer = False except Exception as e: coop_logger.error("Shop, isCartRespectingTimeSlotContraints : %s, %s", str(e), str(cart)) return answer @staticmethod def get_orders_by_partner_id(partner_id): result = {} # TODO : Retrieve orders and send them back try: c_db = CouchDB(arg_db='shop') all_docs = c_db.getAllDocs() # TODO use view /reduce p_orders = [] for doc in all_docs: # print(str(doc)) if int(doc['partner']['id']) == int(partner_id): p_orders.append(doc) # orders are sorted by ascending best_date result['orders'] = sorted(p_orders, key=lambda x: x['best_date']) except Exception as e: result['error'] = str(e) return result @staticmethod def get_slots(): slots = {} try: c_db = CouchDB(arg_db='shop') all_docs = c_db.getAllDocs() # TODO use view /reduce for doc in all_docs: d, h = doc['best_date'].split(' ') if d in slots.keys(): if h in slots[d].keys(): slots[d][h] += 1 else: slots[d][h] = 1 else: slots[d] = {h: 1} except Exception as e: coop_logger.error("Shop, get_slots : %s", str(e)) # print(str(sorted(slots.keys()))) return slots @staticmethod def get_shop_settings(): """ Get shop admin settings from json config file """ res = {} try: with open(shop_admin_settings_file) as json_file: res = json.load(json_file) # file automatically closed with 'with..as' statement except Exception as e: res = {} return res @staticmethod def add_shop_closing_date(closing_date): """ Add a closing date to shop settings and return settings """ res = {} default_data = CagetteShop.get_default_shop_admin_settings() try: with open(shop_admin_settings_file) as json_file: data = json.load(json_file) for k in default_data.keys(): if k not in data: data[k] = default_data[k] except: data = default_data data['closing_dates'].append(closing_date) data['closing_dates'].sort() try: with open(shop_admin_settings_file, 'w') as outfile: json.dump(data, outfile) res['shop_settings'] = data except Exception as ee: res['error'] = str(ee) return res return res @staticmethod def remove_shop_closing_date(closing_date): """ Remove a closing date from shop settings and return settings """ res = {} data = {} try: with open(shop_admin_settings_file) as json_file: data = json.load(json_file) data['closing_dates'].remove(closing_date) with open(shop_admin_settings_file, 'w') as outfile: json.dump(data, outfile) res['shop_settings'] = data except Exception as e: res['error'] = str(e) return res @staticmethod def save_max_orders_ps(nb): res = {} default_data = CagetteShop.get_default_shop_admin_settings() try: with open(shop_admin_settings_file) as json_file: data = json.load(json_file) for k in default_data.keys(): if k not in data: data[k] = default_data[k] except: data = default_data data['max_timeslot_carts'] = nb try: with open(shop_admin_settings_file, 'w') as outfile: json.dump(data, outfile) res['shop_settings'] = data except Exception as ee: res['error'] = str(ee) return res @staticmethod def save_capital_message(text): # TODO : Factorize with save_max_orders_ps and add_shop_closing_date res = {} default_data = CagetteShop.get_default_shop_admin_settings() try: with open(shop_admin_settings_file) as json_file: data = json.load(json_file) for k in default_data.keys(): if k not in data: data[k] = default_data[k] except: data = default_data data['capital_message'] = text try: with open(shop_admin_settings_file, 'w') as outfile: json.dump(data, outfile) res['shop_settings'] = data except Exception as ee: res['error'] = str(ee) return res @staticmethod def remove_unused_orders(): res = {'errors': []} try: from datetime import datetime now_unixts = datetime.timestamp(datetime.now()) c_db = CouchDB(arg_db='shop') all_docs = c_db.getAllDocs() # TODO use view /reduce for doc in all_docs: if (doc['state'] == 'init'): try: diff = doc['init_time'] - now_unixts if abs(diff / 3600) > settings.HOURS_FOR_VALIDATION_SHOP + 2: del_res = CagetteShop.deleteCartFromCDB(doc['_id']) if 'error' in del_res: res['errors'].append({'ctx': 'delete', 'msg': del_res['error']}) except Exception as e1: res['errors'].append({'ctx': 'init_doc_loop', 'msg': str(e1)}) coop_logger.error("Shop, remove_unused_orders (e1): %s", str(e1)) except Exception as e2: coop_logger.error("Shop, remove_unused_orders (e2) : %s", str(e2)) res['errors'].append({'ctx': 'main', 'msg': str(e2)}) return res