from flask import Blueprint, render_template, current_app, abort, g, url_for, \ flash, redirect, session, request, jsonify from galatea.tryton import tryton from galatea.csrf import csrf from galatea.utils import thumbnail from galatea.helpers import login_required, customer_required from flask.ext.babel import gettext as _, lazy_gettext, ngettext from flask.ext.wtf import Form from wtforms import TextField, SelectField, IntegerField, validators from trytond.transaction import Transaction from decimal import Decimal from emailvalid import check_email import vatnumber cart = Blueprint('cart', __name__, template_folder='templates') GALATEA_WEBSITE = current_app.config.get('TRYTON_GALATEA_SITE') SHOP = current_app.config.get('TRYTON_SALE_SHOP') SHOPS = current_app.config.get('TRYTON_SALE_SHOPS') CART_CROSSSELLS = current_app.config.get('TRYTON_CART_CROSSSELLS', True) LIMIT_CROSSELLS = current_app.config.get('TRYTON_CATALOG_LIMIT_CROSSSELLS', 10) MINI_CART_CODE = current_app.config.get('TRYTON_CATALOG_MINI_CART_CODE', False) Website = tryton.pool.get('galatea.website') GalateaUser = tryton.pool.get('galatea.user') Template = tryton.pool.get('product.template') Product = tryton.pool.get('product.product') Shop = tryton.pool.get('sale.shop') Carrier = tryton.pool.get('carrier') Party = tryton.pool.get('party.party') Address = tryton.pool.get('party.address') Sale = tryton.pool.get('sale.sale') SaleLine = tryton.pool.get('sale.line') Country = tryton.pool.get('country.country') Subdivision = tryton.pool.get('country.subdivision') Carrier = tryton.pool.get('carrier') PaymentType = tryton.pool.get('account.payment.type') PRODUCT_TYPE_STOCK = ['goods', 'assets'] # TODO stdum library VAT_COUNTRIES = [('', '')] for country in vatnumber.countries(): VAT_COUNTRIES.append((country, country)) class ShipmentAddressForm(Form): "Shipment Address form" shipment_name = TextField(lazy_gettext('Name'), [validators.Required()]) shipment_street = TextField(lazy_gettext('Street'), [validators.Required()]) shipment_city = TextField(lazy_gettext('City'), [validators.Required()]) shipment_zip = TextField(lazy_gettext('Zip'), [validators.Required()]) shipment_country = SelectField(lazy_gettext('Country'), [validators.Required(), ], coerce=int) shipment_subdivision = IntegerField(lazy_gettext('Subdivision'), [validators.Required()]) shipment_email = TextField(lazy_gettext('E-mail'), [validators.Required(), validators.Email()]) shipment_phone = TextField(lazy_gettext('Phone')) vat_country = SelectField(lazy_gettext('VAT Country')) vat_number = TextField(lazy_gettext('VAT Number')) def __init__(self, *args, **kwargs): Form.__init__(self, *args, **kwargs) def validate(self): rv = Form.validate(self) if not rv: return False return True class InvoiceAddressForm(Form): "Shipment Address form" invoice_name = TextField(lazy_gettext('Name'), [validators.Required()]) invoice_street = TextField(lazy_gettext('Street'), [validators.Required()]) invoice_city = TextField(lazy_gettext('City'), [validators.Required()]) invoice_zip = TextField(lazy_gettext('Zip'), [validators.Required()]) invoice_country = SelectField(lazy_gettext('Country'), [validators.Required(), ], coerce=int) invoice_subdivision = IntegerField(lazy_gettext('Subdivision'), [validators.Required()]) invoice_email = TextField(lazy_gettext('E-mail'), [validators.Required(), validators.Email()]) invoice_phone = TextField(lazy_gettext('Phone')) vat_country = SelectField(lazy_gettext('VAT Country')) vat_number = TextField(lazy_gettext('VAT Number')) def __init__(self, *args, **kwargs): Form.__init__(self, *args, **kwargs) def validate(self): rv = Form.validate(self) if not rv: return False return True def get_carriers(shop, party=None, untaxed=0, tax=0, total=0, payment=None): '''Return carriers and calculate delivery price from a virtual sale''' sale = Sale() sale.untaxed_amount = untaxed sale.tax_amount = tax sale.total_amount = total if isinstance(payment, int): sale.payment_type = PaymentType(payment) else: sale.payment_type = payment context = {} context['record'] = sale # Eval by "carrier formula" require "record" carriers = [] decimals = "%0."+str(shop.esale_currency.digits)+"f" # "%0.2f" euro default_party = None if party: if hasattr(party, 'carrier'): carrier = party.carrier if carrier: sale.carrier = carrier context['carrier'] = carrier with Transaction().set_context(context): carrier_price = carrier.get_sale_price() # return price, currency price = carrier_price[0] price_w_tax = carrier.get_sale_price_w_tax(price, party=party) carriers.append({ 'id': party.carrier.id, 'name': party.carrier.rec_name, 'price': float(Decimal(decimals % price)), 'price_w_tax': float(Decimal(decimals % price_w_tax)), }) default_party = party.carrier for c in shop.esale_carriers: carrier = c.carrier if carrier == default_party: continue sale.carrier = carrier context['carrier'] = carrier with Transaction().set_context(context): carrier_price = carrier.get_sale_price() # return price, currency price = carrier_price[0] price_w_tax = carrier.get_sale_price_w_tax(price, party=party) carriers.append({ 'id': carrier.id, 'name': carrier.rec_name, 'price': float(Decimal(decimals % price)), 'price_w_tax': float(Decimal(decimals % price_w_tax)), }) return sorted(carriers, key=lambda k: k['price']) @cart.route('/carriers', methods=['GET'], endpoint="carriers") @tryton.transaction() def carriers(lang): '''Return all carriers (JSON)''' zip = request.args.get('zip', None) untaxed = request.args.get('untaxed', None) tax = request.args.get('tax', None) total = request.args.get('total', None) payment = request.args.get('payment', None) customer = session.get('customer', None) shop = Shop(SHOP) carriers = get_carriers( shop=shop, party=Party(customer) if customer else None, untaxed=Decimal(untaxed) if untaxed else 0, tax=Decimal(tax) if tax else 0, total=Decimal(total) if total else 0, payment=int(payment) if untaxed else None, ) if zip: zip_carriers = [] for carrier in Carrier.get_carriers_from_zip(zip): for c in carriers: if carrier.id == c['id']: zip_carriers.append(c) break carriers = sorted(zip_carriers, key=lambda k: k['price']) return jsonify(result=carriers) @cart.route('/json/my-cart', methods=['GET', 'PUT'], endpoint="my-cart") @tryton.transaction() def my_cart(lang): '''All Carts JSON''' items = [] shop = Shop(SHOP) domain = [ ('sale', '=', None), ('shop', '=', SHOP), ] if session.get('user'): # login user domain.append(['OR', ('sid', '=', session.sid), ('galatea_user', '=', session['user']), ]) else: # anonymous user domain.append( ('sid', '=', session.sid), ) lines = SaleLine.search(domain) decimals = "%0."+str(shop.esale_currency.digits)+"f" # "%0.2f" euro for line in lines: img = line.product.template.esale_default_images image = current_app.config.get('BASE_IMAGE') if img.get('small'): thumbname = img['small']['name'] filename = img['small']['digest'] image = thumbnail(filename, thumbname, '200x200') items.append({ 'id': line.id, 'name': line.product.code if MINI_CART_CODE else line.product.rec_name, 'url': url_for('catalog.product_'+g.language, lang=g.language, slug=line.product.template.esale_slug), 'quantity': line.quantity, 'unit_price': float(Decimal(decimals % line.unit_price)), 'unit_price_w_tax': float(Decimal(decimals % line.unit_price_w_tax)), 'untaxed_amount': float(Decimal(decimals % line.amount)), 'amount_w_tax': float(Decimal(decimals % line.amount_w_tax)), 'image': image, }) return jsonify(result={ 'currency': shop.esale_currency.symbol, 'items': items, }) @cart.route("/confirm/", methods=["POST"], endpoint="confirm") @tryton.transaction() def confirm(lang): '''Convert carts to sale order Return to Sale Details ''' shop = Shop(SHOP) data = request.form party = session.get('customer') invoice_address = data.get('invoice_address') shipment_address = data.get('shipment_address') # Get all carts domain = [ ('sale', '=', None), ('shop', '=', SHOP), ] if session.get('user'): # login user domain.append(['OR', ('sid', '=', session.sid), ('galatea_user', '=', session['user']), ]) else: # anonymous user domain.append( ('sid', '=', session.sid), ) lines = SaleLine.search(domain) if not lines: flash(_('There are not products in your cart.'), 'danger') return redirect(url_for('.cart', lang=g.language)) # New party if party: party = Party(party) else: name = data.get('invoice_name') or data.get('shipment_name') email = data.get('invoice_email') or data.get('shipment_email') if not check_email(email): flash(_('Email "{email}" is not valid.').format( email=email), 'danger') return redirect(url_for('.cart', lang=g.language)) party = Party.esale_create_party(shop, { 'name': name, 'esale_email': email, 'vat_country': data.get('vat_country', None), 'vat_number': data.get('vat_number', None), }) session['customer'] = party.id if invoice_address: if invoice_address != 'new-address': invoice_address = Address(invoice_address) else: country = None if data.get('invoice_country'): country = int(data.get('invoice_country')) subdivision = None if data.get('invoice_subdivision'): subdivision = int(data.get('invoice_subdivision')) values = { 'name': data.get('invoice_name'), 'street': data.get('invoice_street'), 'city': data.get('invoice_city'), 'zip': data.get('invoice_zip'), 'country': country, 'subdivision': subdivision, 'phone': data.get('invoice_phone'), 'email': data.get('invoice_email'), 'fax': None, } if shipment_address == 'invoice_address': values['delivery'] = True invoice_address = Address.esale_create_address( shop, party, values, type='invoice') if shipment_address == 'invoice_address': if invoice_address: shipment_address = invoice_address else: flash(_('You have selected that the delivery address is ' \ 'the invoice address. Select an Invoice Address.'), 'danger') return redirect(url_for('.cart', lang=g.language)) elif shipment_address != 'new-address': shipment_address = Address(shipment_address) else: country = None if data.get('shipment_country'): country = int(data.get('shipment_country')) subdivision = None if data.get('shipment_subdivision'): subdivision = int(data.get('shipment_subdivision')) values = { 'name': data.get('shipment_name'), 'street': data.get('shipment_street'), 'city': data.get('shipment_city'), 'zip': data.get('shipment_zip'), 'country': country, 'subdivision': subdivision, 'phone': data.get('shipment_phone'), 'email': data.get('shipment_email'), 'fax': None, } if not invoice_address: values['invoice'] = True shipment_address = Address.esale_create_address( shop, party, values, type='delivery') # Create new sale values = {} values['esale'] = True values['shipment_cost_method'] = 'order' # force shipment invoice on order if invoice_address: values['invoice_address'] = invoice_address values['shipment_address'] = shipment_address payment_type = data.get('payment_type') if payment_type: values['payment_type'] = int(payment_type) carrier = data.get('carrier') if carrier: values['carrier'] = int(carrier) comment = data.get('comment') if comment: values['comment'] = comment if session.get('user'): # login user values['galatea_user'] = session['user'] sale = Sale.get_sale_data(party) # Add shipment line carrier_price = data.get('carrier-cost') if carrier_price: product = shop.esale_delivery_product shipment_price = Decimal(carrier_price) shipment_line = SaleLine.get_shipment_line(product, shipment_price, sale, party) lines.append(shipment_line) # Add lines to sale sale.lines = lines # TODO try/except error sale.save() #~ if error: #~ if not session.get('logged_in') and session.get('customer'): #~ session.pop('customer', None) #~ current_app.logger.error('Sale. Error create sale from party (%s): %s' % (party.id, error)) if not sale: flash(_('It has not been able to convert the cart into an order. ' \ 'Try again or contact us.'), 'danger') return redirect(url_for('.cart', lang=g.language)) # sale draft to quotation try: Sale.quote([sale]) except Exception as e: current_app.logger.info(e) if current_app.debug: current_app.logger.info('Sale. Create sale %s' % sale.id) flash(_('Successfully created a new order.'), 'success') return redirect(url_for('sale.sale', lang=g.language, id=sale.id)) @csrf.exempt @cart.route("/add/", methods=["POST"], endpoint="add") @tryton.transaction() def add(lang): '''Add product item cart''' websites = Website.search([ ('id', '=', GALATEA_WEBSITE), ], limit=1) if not websites: abort(404) website, = websites to_create = [] to_update = [] to_remove = [] to_remove_products = [] # Products in older cart and don't sell # Convert form values to dict values {'id': 'qty'} values = {} codes = [] # json request if request.json: for data in request.json: if data.get('name'): prod = data.get('name').split('-') try: qty = float(data.get('value')) except: qty = 1 try: values[int(prod[1])] = qty except: values[prod[1]] = qty codes.append(prod[1]) if not values: return jsonify(result=False) # post request else: for k, v in request.form.iteritems(): prod = k.split('-') if prod[0] == 'product': try: qty = float(v) except: flash(_('You try to add no numeric quantity. ' \ 'The request has been stopped.')) return redirect(url_for('.cart', lang=g.language)) try: values[int(prod[1])] = qty except: values[prod[1]] = qty codes.append(prod[1]) # transform product code to id if codes: products = Product.search([('code', 'in', codes)]) # reset dict vals = values.copy() values = {} for k, v in vals.items(): for prod in products: if prod.code == k: values[prod['id']] = v break # Remove items in cart removes = request.form.getlist('remove') # Products Current User Cart (products to send) products_current_cart = [k for k,v in values.iteritems()] # Search current cart by user or session domain = [ ('sale', '=', None), ('shop', '=', SHOP), ('product.id', 'in', products_current_cart) ] if session.get('user'): # login user domain.append(['OR', ('sid', '=', session.sid), ('galatea_user', '=', session['user']), ]) else: # anonymous user domain.append( ('sid', '=', session.sid), ) lines = SaleLine.search(domain) # Products Current Cart (products available in sale.cart) products_in_cart = [l.product.id for l in lines] # Get product data products = Product.search([ ('id', 'in', products_current_cart), ('template.esale_available', '=', True), ('template.esale_active', '=', True), ('template.shops', 'in', [SHOP]), ]) # Delete products data if removes: for remove in removes: for line in lines: try: if line.id == int(remove): to_remove.append(line) break except: flash(_('You try to remove no numeric cart. ' \ 'The request has been stopped.')) return redirect(url_for('.cart', lang=g.language)) # Add/Update products data for product_id, qty in values.iteritems(): product = None for p in products: if p.id == product_id: product = p break if not product or not product.add_cart: continue # Add cart if have stock if website.esale_stock: if website.esale_stock_qty == 'forecast_quantity': quantity = product.esale_forecast_quantity else: quantity = product.esale_quantity if product.type in PRODUCT_TYPE_STOCK and not (quantity > 0 and qty <= quantity): flash(_('Not enought stock for the product "{product}" (maximun: {quantity} units).').format( product=product.rec_name, quantity=quantity), 'danger') continue line = SaleLine() defaults = line.default_get(line._fields.keys(), with_rec_name=False) for key in defaults: setattr(line, key, defaults[key]) line.party = session.get('customer', None) line.quantity = qty line.product = product.id line.sid = session.sid line.shop = SHOP line.galatea_user = session.get('user', None) line.on_change_product() # Create data if product_id not in products_in_cart and qty > 0: to_create.append(line._save_values) # Update data if product_id in products_in_cart: for line in lines: if line.product.id == product_id: if qty > 0: line.quantity = qty line.on_change_quantity() to_update.extend(([line], line._save_values)) else: # Remove data when qty <= 0 to_remove.append(line) break # Add to remove older products if to_remove_products: for remove in to_remove_products: for line in lines: if line.product.id == remove: to_remove.append(line) break # Add Cart if to_create: SaleLine.create(to_create) flash(ngettext( '%(num)s product has been added in your cart.', '%(num)s products have been added in your cart.', len(to_create)), 'success') # Update Cart if to_update: SaleLine.write(*to_update) total = len(to_update)/2 if to_remove: total = total-len(to_remove) flash(ngettext( '%(num)s product has been updated in your cart.', '%(num)s products have been updated in your cart.', total), 'success') # Delete Cart if to_remove: SaleLine.delete(to_remove) flash(ngettext( '%(num)s product has been deleted in your cart.', '%(num)s products have been deleted in your cart.', len(to_remove)), 'success') if request.json: # Add JSON messages (success, warning) success = [] warning = [] for f in session.get('_flashes', []): if f[0] == 'success': success.append(f[1]) else: warning.append(f[1]) messages = {} messages['success'] = ",".join(success) messages['warning'] = ",".join(warning) session.pop('_flashes', None) return jsonify(result=True, messages=messages) else: return redirect(url_for('.cart', lang=g.language)) @cart.route("/checkout/", methods=["GET", "POST"], endpoint="checkout") @tryton.transaction() def checkout(lang): '''Checkout user or session''' websites = Website.search([ ('id', '=', GALATEA_WEBSITE), ], limit=1) if not websites: abort(404) website, = websites values = {} errors = [] shop = Shop(SHOP) email = request.form.get('invoice_email') or request.form.get('shipment_email') domain = [ ('sale', '=', None), ('shop', '=', SHOP), ] if session.get('user'): # login user domain.append(['OR', ('sid', '=', session.sid), ('galatea_user', '=', session['user']), ]) else: # anonymous user domain.append( ('sid', '=', session.sid), ) lines = SaleLine.search(domain) if not lines: flash(_('There are not products in your cart.'), 'danger') return redirect(url_for('.cart', lang=g.language)) # search user same email request if not session.get('logged_in') and request.form.get('shipment_email'): users = GalateaUser.search([ ('email', '=', email), ('active', '=', True), ('websites', 'in', [GALATEA_WEBSITE]), ], limit=1) if users: flash(_('Your email is already registed user. Please, login in.'), 'danger') return redirect(url_for('.cart', lang=g.language)) untaxed_amount = Decimal(0) tax_amount = Decimal(0) total_amount = Decimal(0) for line in lines: untaxed_amount += line.amount tax_amount += line.amount_w_tax - line.amount total_amount += line.amount_w_tax # checkout stock available if website.esale_stock: if line.product.type not in PRODUCT_TYPE_STOCK: continue if website.esale_stock_qty == 'forecast_quantity': quantity = line.product.esale_forecast_quantity else: quantity = line.product.esale_quantity if not (line.quantity > 0 and line.quantity <= quantity): flash(_('Not enought stock for the product "{product}" (maximun: {quantity} units).').format( product=line.product.rec_name, quantity=quantity), 'danger') return redirect(url_for('.cart', lang=g.language)) party = None if session.get('customer'): party = Party(session.get('customer')) # Invoice Address invoice_address = request.form.get('invoice_address') if invoice_address: values['invoice_address'] = invoice_address if invoice_address == 'new-address': values['invoice_name'] = request.form.get('invoice_name') values['invoice_street'] = request.form.get('invoice_street') values['invoice_zip'] = request.form.get('invoice_zip') values['invoice_city'] = request.form.get('invoice_city') values['invoice_phone'] = request.form.get('invoice_phone') if session.get('email'): values['invoice_email'] = session['email'] else: invoice_email = request.form.get('invoice_email') if not check_email(invoice_email): errors.append(_('Email not valid.')) values['invoice_email'] = invoice_email invoice_country = request.form.get('invoice_country') if invoice_country: values['invoice_country'] = invoice_country country, = Country.browse([invoice_country]) values['invoice_country_name'] = country.name invoice_subdivision = request.form.get('invoice_subdivision') if invoice_subdivision: values['invoice_subdivision'] = invoice_subdivision subdivision, = Subdivision.browse([invoice_subdivision]) values['invoice_subdivision_name'] = subdivision.name if not values['invoice_name'] or not values['invoice_street'] \ or not values['invoice_zip'] or not values['invoice_city'] \ or not values['invoice_email']: errors.append(_('Error when validate Invoice Address. ' \ 'Please, return to cart and complete Invoice Address')) elif party: addresses = Address.search([ ('party', '=', party), ('id', '=', int(invoice_address)), ('active', '=', True), ], order=[('sequence', 'ASC'), ('id', 'ASC')]) if addresses: address, = addresses values['invoice_address_name'] = address.full_address else: errors.append(_('We can not found a related address. ' \ 'Please, select a new address in Invoice Address')) else: errors.append(_('You not select a new address and are not a customer. ' \ 'Please, select a new address in Invoice Address')) # Shipment Address #~ form_shipment_address = ShipmentAddressForm() shipment_address = request.form.get('shipment_address') if not shipment_address: flash(_('Select a Shipment Address.'), 'danger') return redirect(url_for('.cart', lang=g.language)) values['shipment_address'] = shipment_address if shipment_address == 'invoice_address': pass elif shipment_address == 'new-address': values['shipment_name'] = request.form.get('shipment_name') values['shipment_street'] = request.form.get('shipment_street') values['shipment_zip'] = request.form.get('shipment_zip') values['shipment_city'] = request.form.get('shipment_city') values['shipment_phone'] = request.form.get('shipment_phone') if session.get('email'): values['shipment_email'] = session['email'] else: shipment_email = request.form.get('shipment_email') if shipment_email: if not check_email(shipment_email): errors.append(_('Email not valid.')) values['shipment_email'] = shipment_email shipment_country = request.form.get('shipment_country') if shipment_country: values['shipment_country'] = shipment_country country, = Country.browse([shipment_country]) values['shipment_country_name'] = country.name shipment_subdivision = request.form.get('shipment_subdivision') if shipment_subdivision: values['shipment_subdivision'] = shipment_subdivision subdivision, = Subdivision.browse([shipment_subdivision]) values['shipment_subdivision_name'] = subdivision.name if not values['shipment_name'] or not values['shipment_street'] \ or not values['shipment_zip'] or not values['shipment_city']: errors.append(_('Error when validate Shipment Address. ' \ 'Please, return to cart and complete Shipment Address')) vat_country = request.form.get('vat_country') vat_number = request.form.get('vat_number') if vat_number: values['vat_number'] = vat_number if vat_country: values['vat_country'] = vat_country if vat_country and vat_number: vat_number = '%s%s' % (vat_country.upper(), vat_number) if not vatnumber.check_vat(vat_number): errors.append(_('VAT not valid.')) elif party: addresses = Address.search([ ('party', '=', party), ('id', '=', int(shipment_address)), ('active', '=', True), ], order=[('sequence', 'ASC'), ('id', 'ASC')]) if addresses: address, = addresses values['shipment_address_name'] = address.full_address else: errors.append(_('We can not found a related address. ' \ 'Please, select a new address in Shipment Address')) else: errors.append(_('You not select a new address and are not a customer. ' \ 'Please, select a new address in Shipment Address')) # Payment payment = int(request.form.get('payment')) if request.form.get('payment') else None payment_type = None if not payment and (party and hasattr(party, 'customer_payment_type')): if party.customer_payment_type: payment_type = party.customer_payment_type values['payment'] = payment_type.id values['payment_name'] = payment_type.rec_name if not payment_type: for p in shop.esale_payments: if p.payment_type.id == payment: payment_type = p.payment_type values['payment'] = payment_type.id values['payment_name'] = payment_type.rec_name break # Carrier carrier_id = request.form.get('carrier') if carrier_id: carrier = Carrier(carrier_id) # create a virtual sale sale = Sale() sale.untaxed_amount = untaxed_amount sale.tax_amount = tax_amount sale.total_amount = total_amount sale.carrier = carrier sale.payment_type = payment_type context = {} context['record'] = sale # Eval by "carrier formula" require "record" context['carrier'] = carrier with Transaction().set_context(context): carrier_price = carrier.get_sale_price() # return price, currency price = carrier_price[0] price_w_tax = carrier.get_sale_price_w_tax(price, party=party) values['carrier'] = carrier values['carrier_name'] = carrier.rec_name values['carrier_cost'] = price values['carrier_cost_w_tax'] = price_w_tax # Comment values['comment'] = request.form.get('comment') # Breadcumbs breadcrumbs = [{ 'slug': url_for('.cart', lang=g.language), 'name': _('Cart'), }, { 'slug': url_for('.cart', lang=g.language), 'name': _('Checkout'), }] return render_template('checkout.html', website=website, breadcrumbs=breadcrumbs, shop=shop, lines=lines, values=values, errors=errors, prices={ 'untaxed_amount': untaxed_amount, 'tax_amount': tax_amount, 'total_amount': total_amount, }, ) @cart.route("/", endpoint="cart") @tryton.transaction() def cart_list(lang): '''Cart by user or session''' websites = Website.search([ ('id', '=', GALATEA_WEBSITE), ], limit=1) if not websites: abort(404) website, = websites shop = Shop(SHOP) form_invoice_address = InvoiceAddressForm( country=shop.esale_country.id, vat_country=shop.esale_country.code) countries = [(c.id, c.name) for c in shop.esale_countrys] form_invoice_address.invoice_country.choices = countries form_invoice_address.vat_country.choices = VAT_COUNTRIES form_shipment_address = ShipmentAddressForm( country=shop.esale_country.id, vat_country=shop.esale_country.code) countries = [(c.id, c.name) for c in shop.esale_countrys] form_shipment_address.shipment_country.choices = countries form_shipment_address.vat_country.choices = VAT_COUNTRIES domain = [ ('sale', '=', None), ('shop', '=', SHOP), ] if session.get('user'): # login user domain.append(['OR', ('sid', '=', session.sid), ('galatea_user', '=', session['user']), ]) else: # anonymous user domain.append( ('sid', '=', session.sid), ) lines = SaleLine.search(domain) products = [] untaxed_amount = Decimal(0) tax_amount = Decimal(0) total_amount = Decimal(0) for line in lines: products.append(line.product.id) untaxed_amount += line.amount tax_amount += line.amount_w_tax - line.amount total_amount += line.amount_w_tax party = None addresses = [] delivery_addresses = [] invoice_addresses = [] if session.get('customer'): party = Party(session['customer']) for address in party.addresses: addresses.append(address) if address.delivery: delivery_addresses.append(address) if address.invoice: invoice_addresses.append(address) default_invoice_address = None default_delivery_address = None if session.get('user'): user = GalateaUser(session['user']) if user.invoice_address: default_invoice_address = user.invoice_address if user.shipment_address: default_delivery_address = user.shipment_address # Get payments. Shop payments or Party payment payments = [] default_payment = None if shop.esale_payments: default_payment = shop.esale_payments[0].payment_type payments = [payment.payment_type for payment in shop.esale_payments] if party: if hasattr(party, 'customer_payment_type'): if party.customer_payment_type: default_payment = party.customer_payment_type if party and hasattr(party, 'customer_payment_type'): customer_payment = party.customer_payment_type if customer_payment and not customer_payment in payments: payments.append(customer_payment) # Get carriers. Shop carriers or Party carrier stockable = Carrier.get_products_stockable(products) carriers = [] if stockable: carriers = get_carriers( shop=shop, party=party, untaxed=untaxed_amount, tax=tax_amount, total=total_amount, payment=default_payment) # Cross Sells crossells = [] if CART_CROSSSELLS: template_ids = list({l.product.template.id for l in lines}) templates = Template.browse(template_ids) crossells_ids = set() for template in templates: for crossell in template.esale_crosssells_by_shop: crossells_ids.add(crossell.id) if crossells_ids: crossells = Template.browse(list(crossells_ids)[:LIMIT_CROSSELLS]) # Breadcumbs breadcrumbs = [{ 'slug': url_for('.cart', lang=g.language), 'name': _('Cart'), }] return render_template('cart.html', website=website, breadcrumbs=breadcrumbs, shop=shop, lines=lines, form_invoice_address=form_invoice_address, form_shipment_address=form_shipment_address, party=party, addresses=addresses, delivery_addresses=delivery_addresses, default_delivery_address=default_delivery_address, invoice_addresses=invoice_addresses, default_invoice_address=default_invoice_address, crossells=crossells, payments=payments, default_payment=default_payment, carriers=carriers, stockable=stockable, prices={ 'untaxed_amount': untaxed_amount, 'tax_amount': tax_amount, 'total_amount': total_amount, }, ) @cart.route("/pending", endpoint="cart-pending") @login_required @tryton.transaction() def cart_pending(lang): '''Last cart pending''' domain = [ ('sale', '=', None), ('shop', '=', SHOP), ['OR', ('party', '=', session['customer']), ('galatea_user', '=', session['user']), ] ] lines = SaleLine.search(domain, offset=0, limit=10) breadcrumbs = [{ 'slug': url_for('.cart', lang=g.language), 'name': _('Cart'), }, { 'name': _('Pending'), }] return render_template('cart-pending.html', lines=lines, breadcrumbs=breadcrumbs, ) @cart.route("/clone/", methods=["POST"], endpoint="clone") @login_required @customer_required @tryton.transaction() def clone(lang): '''Copy Sale Lines to new carts''' id = request.form.get('id') if not id: flash(_('Error when clone. Select a sale to clone.'), "danger") return redirect(url_for('.sales', lang=g.language)) sales = Sale.search([ ('id', '=', id), ('shop', 'in', SHOPS), ('party', '=', session['customer']), ], limit=1) if not sales: flash(_('Error when clone. You not have permisions to clone.'), "danger") return redirect(url_for('.sales', lang=g.language)) sale, = sales products = set() for l in sale.lines: if l.product and l.product.esale_available: products.add(l.product.id) # Search current carts by user or session domain = [ ('sale', '=', None), ('shop', '=', SHOP), ] if session.get('user'): # login user domain.append(['OR', ('sid', '=', session.sid), ('galatea_user', '=', session['user']), ]) else: # anonymous user domain.append( ('sid', '=', session.sid), ) lines = SaleLine.search(domain) # remove products that exist in current cart for l in lines: if l.product.id in products: products.remove(l.product.id) to_create = [] for product_id in products: line = SaleLine() defaults = line.default_get(line._fields.keys(), with_rec_name=False) for key in defaults: setattr(line, key, defaults[key]) line.party = sale.party.id line.quantity = 1 line.product = product_id line.sid = session.sid line.galatea_user = session.get('user', None) line.on_change_product() line.shop = SHOP to_create.append(line._save_values) if to_create: SaleLine.create(to_create) flash(ngettext( '%(num)s product has been added in your cart.', '%(num)s products have been added in your cart.', len(to_create)), 'success') return redirect(url_for('.cart', lang=g.language))