diff options
| author | IT Fixcomart <it@fixcomart.co.id> | 2023-07-24 03:31:59 +0000 |
|---|---|---|
| committer | IT Fixcomart <it@fixcomart.co.id> | 2023-07-24 03:31:59 +0000 |
| commit | c1ac22304b557b9982b5fb79d23d29b6faf48090 (patch) | |
| tree | a1e6db531a5bf19994374e895d5d8a8c9abf1ffa | |
| parent | 6966c00bf2ef2bd9c2261d9363ac6b463a7766dd (diff) | |
| parent | 30909e82d7ff1f3cac4700e284f80552a0d38523 (diff) | |
Merged in feature/voucher-cart (pull request #65)
Feature/voucher cart
28 files changed, 862 insertions, 148 deletions
diff --git a/indoteknik_api/controllers/api_v1/cart.py b/indoteknik_api/controllers/api_v1/cart.py index a8628432..0265ec57 100644 --- a/indoteknik_api/controllers/api_v1/cart.py +++ b/indoteknik_api/controllers/api_v1/cart.py @@ -5,55 +5,70 @@ from odoo.http import request class Cart(controller.Controller): prefix = '/api/v1/' + PREFIX_USER = prefix + 'user/<user_id>/' - @http.route(prefix + 'cart', auth='public', methods=['GET']) + @http.route(PREFIX_USER + 'cart', auth='public', methods=['GET', 'OPTIONS']) @controller.Controller.must_authorized() - def get_cart_by_user_id(self, **kw): - user_id = int(kw.get('user_id', 0)) + def get_cart_by_user_id(self, user_id, **kw): + user_cart = request.env['website.user.cart'] + user_id = int(user_id) limit = int(kw.get('limit', 0)) offset = int(kw.get('offset', 0)) query = [('user_id', '=', user_id)] - carts = request.env['website.user.cart'].search(query, limit=limit, offset=offset, order='create_date desc') + carts = user_cart.search(query, limit=limit, offset=offset, order='create_date desc') data = { - 'product_total': request.env['website.user.cart'].search_count(query), - 'products': [] + 'product_total': user_cart.search_count(query), + 'products': carts.get_products() } - for cart in carts: - product = request.env['product.product'].api_single_response(cart.product_id) - product['template_id'] = cart.product_id.product_tmpl_id.id - product['quantity'] = cart.qty - data['products'].append(product) return self.response(data) - @http.route(prefix + 'cart/create-or-update', auth='public', methods=['POST'], csrf=False) + @http.route(PREFIX_USER + 'cart/count', auth='public', methods=['GET', 'OPTIONS']) @controller.Controller.must_authorized() - def create_or_update_cart(self, **kw): - user_id = int(kw.get('user_id', 0)) + def get_cart_count_by_user_id(self, user_id, **kw): + user_id = int(user_id) + query = [('user_id', '=', user_id)] + carts = request.env['website.user.cart'].search_count(query) + return self.response(carts) + + @http.route(PREFIX_USER + 'cart/create-or-update', auth='public', methods=['POST', 'OPTIONS'], csrf=False) + @controller.Controller.must_authorized() + def create_or_update_cart(self, user_id, **kw): + user_id = int(user_id) product_id = int(kw.get('product_id', 0)) qty = int(kw.get('qty', 0)) + is_selected = kw.get('selected', False) + program_line_id = kw.get('program_line_id', False) + program_line_id = False if program_line_id == 'null' or not program_line_id else int(program_line_id) + if is_selected: + is_selected = True if is_selected == 'true' else False if not user_id or not product_id or not qty: return self.response(code=400, description='user_id, product_id and qty is required') query = [('user_id', '=', user_id), ('product_id', '=', product_id)] cart = request.env['website.user.cart'].search(query, limit=1) result = {} + data_to_update = { + 'qty': qty, + 'is_selected': is_selected, + 'program_line_id': program_line_id + } if cart: - cart.write({'qty': qty}) + cart.write(data_to_update) result['id'] = cart.id else: create = request.env['website.user.cart'].create({ 'user_id': user_id, 'product_id': product_id, - 'qty': qty + **data_to_update }) result['id'] = create.id return self.response(result) - @http.route(prefix + 'cart', auth='public', methods=['DELETE'], csrf=False) + @http.route(PREFIX_USER + 'cart', auth='public', methods=['DELETE', 'OPTIONS'], csrf=False) @controller.Controller.must_authorized() - def delete_cart_by_user_id(self, **kw): - user_id = int(kw.get('user_id', 0)) + def delete_cart_by_user_id(self, user_id, **kw): + user_id = int(user_id) query = [('user_id', '=', user_id)] product_ids = kw.get('product_ids') if product_ids: diff --git a/indoteknik_api/controllers/api_v1/product_variant.py b/indoteknik_api/controllers/api_v1/product_variant.py index 999ced6f..8de4669e 100644 --- a/indoteknik_api/controllers/api_v1/product_variant.py +++ b/indoteknik_api/controllers/api_v1/product_variant.py @@ -2,6 +2,7 @@ from .. import controller from odoo import http from odoo.http import request + class ProductVariant(controller.Controller): prefix = '/api/v1/' @@ -20,3 +21,14 @@ class ProductVariant(controller.Controller): return self.response(data) + @http.route(prefix + 'product_variant/<product_id>/promotions', auth='public', methods=['GET', 'OPTIONS']) + @controller.Controller.must_authorized() + def get_product_variant_promotions(self, product_id): + product_id = int(product_id) + user_data = self.verify_user_token() + + program_line = request.env['promotion.program.line'] + program_lines = program_line.get_active_promotions(product_id) + program_lines = program_lines.res_format(user=user_data) + + return self.response(program_lines) diff --git a/indoteknik_api/controllers/api_v1/promotion.py b/indoteknik_api/controllers/api_v1/promotion.py index b137fe2e..f84b8c1c 100644 --- a/indoteknik_api/controllers/api_v1/promotion.py +++ b/indoteknik_api/controllers/api_v1/promotion.py @@ -1,12 +1,13 @@ from .. import controller from odoo import http from odoo.http import request -import ast +from datetime import datetime class Promotion(controller.Controller): prefix = '/api/v1/' - + + @http.route(prefix + 'promotion/<id>', auth='public', methods=['GET']) @controller.Controller.must_authorized() def get_promotion_by_id(self, **kw): @@ -14,7 +15,7 @@ class Promotion(controller.Controller): id = kw.get('id') if not id: return self.response(code=400, description='id is required') - + data = {} id = int(id) coupon_program = request.env['coupon.program'].search([('id', '=', id)]) @@ -24,6 +25,67 @@ class Promotion(controller.Controller): 'image': base_url + 'api/image/coupon.program/x_studio_image_promo/' + str(coupon_program.id) if coupon_program.x_studio_image_promo else '', 'name': coupon_program.name, } + + return self.response(data) + + + @http.route(prefix + 'promotion/home', auth='public', methods=['GET', 'OPTIONS']) + @controller.Controller.must_authorized() + def v1_get_promotion_home(self): + current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + programs = request.env['promotion.program'].search([ + ('start_time', '<=', current_time), + ('end_time', '>=', current_time), + ('program_line.display_on_homepage', '=', True) + ]) + + if not programs: + return self.response(None) + + data = [] + for program in programs: + data_program = { + 'id': program.id, + 'name': program.name, + 'banner': request.env['ir.attachment'].api_image('promotion.program', 'banner', program.id), + 'icon': request.env['ir.attachment'].api_image('promotion.program', 'icon', program.id) + } + data.append(data_program) + + return self.response(data) + + + @http.route(prefix + 'promotion/home/<id>', auth='public', methods=['GET', 'OPTIONS']) + @controller.Controller.must_authorized() + def v1_get_promotion_home_detail(self, id): + program_lines = request.env['promotion.program.line'].search([ + ('display_on_homepage', '=', True), + ('promotion_type', '=', 'special_price'), + ('program_id', '=', int(id)) + ]) + data = [] + for line in program_lines: + product = request.env['product.product'].v2_api_single_response(line.product_id) + product_template = line.product_id.product_tmpl_id + + product.update({ + 'id': product['parent']['id'], + 'image': product['parent']['image'], + 'name': product['parent']['name'], + 'variant_total': len(product_template.product_variant_ids), + 'lowest_price': line.calculate_price(product['price']), + 'stock_total': product['stock'], + 'icon': { + 'top': request.env['ir.attachment'].api_image('promotion.program', 'icon_top', line.program_id.id), + 'bottom': request.env['ir.attachment'].api_image('promotion.program', 'icon_bottom', line.program_id.id) + } + }) + + product.pop('parent', None) + product.pop('price', None) + product.pop('stock', None) + data.append(product) + return self.response(data) -
\ No newline at end of file + diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py index e036751e..6c878197 100644 --- a/indoteknik_api/controllers/api_v1/sale_order.py +++ b/indoteknik_api/controllers/api_v1/sale_order.py @@ -231,21 +231,24 @@ class SaleOrder(controller.Controller): sale_order.state = 'cancel' data = sale_order.id return self.response(data) - + + @http.route(prefix + 'user/<user_id>/sale_order/checkout', auth='public', method=['GET', 'OPTIONS'], csrf=False) + @controller.Controller.must_authorized(private=True, private_key='user_id') + def get_user_checkout_so(self, user_id, **kw): + cart = request.env['website.user.cart'] + voucher_code = kw.get('voucher') + voucher = request.env['voucher'].search([('code', '=', voucher_code)], limit=1) + result = cart.get_user_checkout(user_id, voucher) + return self.response(result) + @http.route(PREFIX_PARTNER + 'sale_order/checkout', auth='public', method=['POST', 'OPTIONS'], csrf=False) @controller.Controller.must_authorized(private=True, private_key='partner_id') def create_partner_sale_order(self, **kw): config = request.env['ir.config_parameter'] - product_pricelist_default_discount_id = int( - config.get_param('product.pricelist.default_discount_id')) - product_pricelist_tier1 = int( - config.get_param('product.pricelist.tier1')) - product_pricelist_tier2 = int( - config.get_param('product.pricelist.tier2')) - product_pricelist_tier3 = int( - config.get_param('product.pricelist.tier3')) + product_pricelist_default_discount_id = int(config.get_param('product.pricelist.default_discount_id')) params = self.get_request_params(kw, { + 'user_id': ['number'], 'partner_id': ['number'], 'partner_shipping_id': ['required', 'number'], 'partner_invoice_id': ['required', 'number'], @@ -258,7 +261,7 @@ class SaleOrder(controller.Controller): 'delivery_service_type': [], 'voucher': [] }) - + if not params['valid']: return self.response(code=400, description=params) @@ -290,68 +293,78 @@ class SaleOrder(controller.Controller): if params['value']['type'] == 'sale_order': parameters['approval_status'] = 'pengajuan1' sale_order = request.env['sale.order'].create([parameters]) - order_line = json.loads(params['value']['order_line']) parameters = [] - partner = request.env['res.partner'].browse( - params['value']['partner_id']) - partner_pricelist = partner.property_product_pricelist - for line in order_line: - product = request.env['product.product'].search( - [('id', '=', line['product_id'])], limit=1) - discount = product._get_website_disc(0) - - price_tier = False - pricelist = { - 'tier1': product._get_pricelist_tier1, - 'tier2': product._get_pricelist_tier2, - 'tier3': product._get_pricelist_tier3, - } - partner_pricelist_id = partner_pricelist.id if partner_pricelist else False - if partner_pricelist_id == product_pricelist_tier1: - price_tier = 'tier1' - if partner_pricelist_id == product_pricelist_tier2: - price_tier = 'tier2' - if partner_pricelist_id == product_pricelist_tier3: - price_tier = 'tier3' - - if price_tier: - price = pricelist[price_tier]() - discount_key = 'discount_%s' % price_tier - if price[discount_key] > 0: - discount = price[discount_key] - - flashsale = product._get_flashsale_price() - flashsale_discount = flashsale['flashsale_discount'] - if flashsale_discount > 0 and flashsale_discount > discount: - discount = flashsale_discount - - parameters.append({ + user_id = params['value']['user_id'] + user_cart = request.env['website.user.cart'] + products = user_cart.get_product_by_user(user_id=user_id, selected=True) + for product in products: + total_qty = product['quantity'] + price_unit = product['price']['price'] + price_discount =product['price']['discount_percentage'] + if product['program'] and product['program']['type']['value'] != 'special_price': + total_qty += sum(x['quantity'] for x in product['program']['items']) + price_unit = product['subtotal'] / total_qty + price_discount = 0 + + param = { 'company_id': 1, 'order_id': sale_order.id, - 'product_id': line['product_id'], - 'product_uom_qty': line['quantity'], - 'price_unit': product._get_website_price_exclude_tax(), - 'discount': discount - }) + 'price_unit': price_unit, + 'discount': price_discount + } + + primary_product = { + **param, + 'product_id': product['id'], + 'product_uom_qty': product['quantity'] + } + if product['program']: + primary_product.update({ + 'program_line_id': product['program']['id'] + }) + parameters.append(primary_product) + + if not product['program']: + continue + + for item in product['program']['items']: + parameters.append({ + **param, + 'product_id': item['id'], + 'product_uom_qty': item['quantity'], + }) request.env['sale.order.line'].create(parameters) - amount_untaxed = sale_order.amount_untaxed - voucher = request.env['voucher'].search([ - ('code', '=', params['value']['voucher']) - ]) + voucher_code = params['value']['voucher'] + voucher = request.env['voucher'].search([('code', '=', voucher_code)]) if voucher: - sale_order.voucher_id = voucher.id + amount_untaxed = 0 + manufacture_ids = [x.id for x in voucher.manufacture_ids] + for line in sale_order.order_line: + manufacture_id = line.product_id.x_manufacture.id or False + if len(manufacture_ids) == 0 or manufacture_id in manufacture_ids: + amount_untaxed += line.price_subtotal + voucher_discount = voucher.calculate_discount(amount_untaxed) - total_qty = sum(line.product_uom_qty for line in sale_order.order_line) - voucher_discount_item = voucher_discount / total_qty + sale_order.voucher_id = voucher.id + sale_order.amount_voucher_disc = voucher_discount + for line in sale_order.order_line: - discount_decimal = line.discount / 100 - voucher_discount_line = voucher_discount_item / (1 - discount_decimal) - price = line.price_unit - voucher_discount_line - line.price_unit = price + manufacture_id = line.product_id.x_manufacture.id or False + if len(manufacture_ids) > 0 and manufacture_id not in manufacture_ids: + continue + voucher_discount_line = line.price_subtotal / amount_untaxed * voucher_discount + line.amount_voucher_disc = voucher_discount_line + discount_decimal = line.discount / 100 + voucher_discount_item = voucher_discount_line / line.product_uom_qty + voucher_disc_before_line_disc = voucher_discount_item / (1 - discount_decimal) + line.price_unit -= voucher_disc_before_line_disc + + cart_ids = [x['cart_id'] for x in products] + user_cart.browse(cart_ids).unlink() return self.response({ 'id': sale_order.id, 'name': sale_order.name diff --git a/indoteknik_api/controllers/api_v1/voucher.py b/indoteknik_api/controllers/api_v1/voucher.py index 0990a1a0..a6a88cad 100644 --- a/indoteknik_api/controllers/api_v1/voucher.py +++ b/indoteknik_api/controllers/api_v1/voucher.py @@ -2,21 +2,76 @@ from .. import controller from odoo import http from odoo.http import request + class Voucher(controller.Controller): prefix = '/api/v1/' - @http.route(prefix + 'voucher', auth='public', methods=['GET', 'OPTIONS']) - @controller.Controller.must_authorized() + @http.route(prefix + 'user/<user_id>/voucher', auth='public', methods=['GET', 'OPTIONS']) + @controller.Controller.must_authorized(private=True, private_key='user_id') def get_vouchers(self, **kw): + cart = request.env['website.user.cart'] code = kw.get('code') - visibility = 'public' + user_id = kw.get('user_id') + visibility = ['public'] parameter = [] if code: - visibility = 'private' + visibility.append('private') parameter += [('code', '=', code)] + user_pricelist = request.env.user_pricelist + if user_pricelist: + parameter += [('excl_pricelist_ids', 'not in', [user_pricelist.id])] - parameter += [('visibility', '=', visibility)] + parameter += [('visibility', 'in', visibility)] vouchers = request.env['voucher'].get_active_voucher(parameter) - data = vouchers.res_format() - return self.response(data) + vouchers = vouchers.res_format() + checkout = cart.get_user_checkout(user_id) + + for voucher in vouchers: + apply_status = '' + products = checkout['products'] + min_purchase_amount = voucher['min_purchase_amount'] + max_discount_amount = voucher['max_discount_amount'] + discount_type = voucher['discount_type'] + discount_amount = voucher['discount_amount'] + can_apply = True + difference_to_apply = 0 + + manufacture_ids = voucher['manufacture_ids'] + subtotal = 0 + has_match_manufacture = False + for product in products: + price = product['price']['price'] + price_discount = product['price']['price_discount'] + quantity = product['quantity'] + manufacture_id = product['manufacture']['id'] or False + + if len(manufacture_ids) == 0 or manufacture_id in manufacture_ids: + purchase_amt = price * quantity + discount_amt = (price - price_discount) * quantity + subtotal += purchase_amt - discount_amt + has_match_manufacture = True + + if not has_match_manufacture: + can_apply = False + apply_status = 'UM' + elif subtotal < min_purchase_amount: + can_apply = False + apply_status = 'MPA' + difference_to_apply = min_purchase_amount - subtotal + + discount_voucher = 0 + if discount_type == 'fixed_price': + discount_voucher = discount_amount + if discount_type == 'percentage': + discount_voucher = subtotal * discount_amount / 100 + + if max_discount_amount > 0 and discount_voucher > max_discount_amount: + discount_voucher = max_discount_amount + + voucher['can_apply'] = can_apply + voucher['apply_status'] = apply_status + voucher['discount_voucher'] = discount_voucher + voucher['difference_to_apply'] = difference_to_apply + + return self.response(vouchers) diff --git a/indoteknik_api/controllers/api_v2/product.py b/indoteknik_api/controllers/api_v2/product.py index d9b43bda..19a97dec 100644 --- a/indoteknik_api/controllers/api_v2/product.py +++ b/indoteknik_api/controllers/api_v2/product.py @@ -12,12 +12,10 @@ class V2Product(controller.Controller): if not id: return self.response(code=400, description='id is required') - pricelist = self.user_pricelist() - data = [] id = [int(x) for x in id.split(',')] product_templates = request.env['product.template'].search([('id', 'in', id)]) if product_templates: - data = [request.env['product.template'].v2_api_single_response(x, pricelist=pricelist, with_detail='DEFAULT') for x in product_templates] + data = [request.env['product.template'].v2_api_single_response(x, with_detail='DEFAULT') for x in product_templates] return self.response(data)
\ No newline at end of file diff --git a/indoteknik_api/controllers/api_v2/product_variant.py b/indoteknik_api/controllers/api_v2/product_variant.py index 8a5bbeb1..b74e4936 100644 --- a/indoteknik_api/controllers/api_v2/product_variant.py +++ b/indoteknik_api/controllers/api_v2/product_variant.py @@ -12,13 +12,11 @@ class V2ProductVariant(controller.Controller): if not id: return self.response(code=400, description='id is required') - pricelist = self.user_pricelist() - data = [] id = [int(x) for x in id.split(',')] product_products = request.env['product.product'].search([('id', 'in', id)]) if product_products: - data = [request.env['product.product'].v2_api_single_response(x, pricelist=pricelist) for x in product_products] + data = [request.env['product.product'].v2_api_single_response(x) for x in product_products] return self.response(data) diff --git a/indoteknik_api/controllers/controller.py b/indoteknik_api/controllers/controller.py index 0fcf4814..1e9f01ee 100644 --- a/indoteknik_api/controllers/controller.py +++ b/indoteknik_api/controllers/controller.py @@ -21,6 +21,7 @@ class Controller(http.Controller): def inner_wrapper(*args, **kwargs): self = args[0] auth = self.authenticate() + request.env.user_pricelist = self.user_pricelist() if not auth: return self.unauthorized_response() if private: diff --git a/indoteknik_api/models/product_product.py b/indoteknik_api/models/product_product.py index edf95a58..334b58c5 100644 --- a/indoteknik_api/models/product_product.py +++ b/indoteknik_api/models/product_product.py @@ -29,20 +29,46 @@ class ProductProduct(models.Model): } return data - def v2_api_single_response(self, product_product, pricelist=False): + def v2_api_single_response(self, product_product): + product_template = product_product.product_tmpl_id + data = { + 'id': product_product.id, + 'parent': { + 'id': product_template.id, + 'name': product_template.name, + 'image': self.env['ir.attachment'].api_image('product.template', 'image_256', product_template.id), + }, + 'code': product_product.default_code or '', + 'name': product_product.display_name, + 'price': product_product.calculate_website_price(), + 'stock': product_product.qty_stock_vendor, + 'weight': product_product.weight, + 'attributes': [x.name for x in product_product.product_template_attribute_value_ids], + 'manufacture' : self.api_manufacture(product_product) + } + return data + + def has_active_program(self): + program_line = self.env['promotion.program.line'] + product_promotions = program_line.get_active_promotions(self.id) + return True if len(product_promotions) > 0 else False + + def calculate_website_price(self): + pricelist = self.env.user_pricelist + config = self.env['ir.config_parameter'] product_pricelist_tier1 = int(config.get_param('product.pricelist.tier1')) product_pricelist_tier2 = int(config.get_param('product.pricelist.tier2')) product_pricelist_tier3 = int(config.get_param('product.pricelist.tier3')) - discount_percentage = product_product._get_website_disc(0) - price_discount = product_product._get_website_price_after_disc_and_tax() + discount_percentage = self._get_website_disc(0) + price_discount = self._get_website_price_after_disc_and_tax() price_tier = False pricelists = { - 'tier1': product_product._get_pricelist_tier1, - 'tier2': product_product._get_pricelist_tier2, - 'tier3': product_product._get_pricelist_tier3, + 'tier1': self._get_pricelist_tier1, + 'tier2': self._get_pricelist_tier2, + 'tier3': self._get_pricelist_tier3, } pricelist_id = pricelist.id if pricelist else False if pricelist_id == product_pricelist_tier1: price_tier = 'tier1' @@ -56,36 +82,11 @@ class ProductProduct(models.Model): if price[discount_key] > 0: discount_percentage = price[discount_key] if price[price_key] > 0: price_discount = price[price_key] - flashsale = product_product._get_flashsale_price() - flashsale_price = flashsale['flashsale_price'] - flashsale_discount = flashsale['flashsale_discount'] - if flashsale_price > 0 and flashsale_price < price_discount: - price_discount = flashsale_price - discount_percentage = flashsale_discount - - stock = product_product.qty_stock_vendor - stock = stock if stock > 0 else 1 - product_template = product_product.product_tmpl_id - data = { - 'id': product_product.id, - 'parent': { - 'id': product_template.id, - 'name': product_template.name, - 'image': self.env['ir.attachment'].api_image('product.template', 'image_256', product_template.id), - }, - 'code': product_product.default_code or '', - 'name': product_product.display_name, - 'price': { - 'price': product_product._get_website_price_exclude_tax(), - 'discount_percentage': discount_percentage, - 'price_discount': price_discount - }, - 'stock': stock, - 'weight': product_product.weight, - 'attributes': [x.name for x in product_product.product_template_attribute_value_ids], - 'manufacture' : self.api_manufacture(product_product) + return { + 'price': self._get_website_price_exclude_tax(), + 'discount_percentage': discount_percentage, + 'price_discount': price_discount } - return data def api_manufacture(self, product_template): if product_template.x_manufacture: diff --git a/indoteknik_api/models/product_template.py b/indoteknik_api/models/product_template.py index 1a345967..68ab79c2 100644 --- a/indoteknik_api/models/product_template.py +++ b/indoteknik_api/models/product_template.py @@ -51,9 +51,7 @@ class ProductTemplate(models.Model): data.update(data_with_detail) return data - def v2_api_single_response(self, product_template, pricelist=False, with_detail=''): - product_pricelist_default_discount_id = self.env['ir.config_parameter'].get_param('product.pricelist.default_discount_id') - product_pricelist_default_discount_id = int(product_pricelist_default_discount_id) + def v2_api_single_response(self, product_template, with_detail=''): data = { 'id': product_template.id, 'image': self.env['ir.attachment'].api_image('product.template', 'image_128', product_template.id), @@ -67,7 +65,7 @@ class ProductTemplate(models.Model): } if with_detail != '': - variants = [self.env['product.product'].v2_api_single_response(variant, pricelist=pricelist) for variant in product_template.product_variant_ids] + variants = [self.env['product.product'].v2_api_single_response(variant) for variant in product_template.product_variant_ids] lowest_price = variants[0]['price'] for variant in variants: if variant["price"]["price_discount"] < lowest_price["price_discount"]: diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index b570810f..ead47bd3 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -75,6 +75,10 @@ 'views/procurement_monitoring_detail.xml', 'views/product_product.xml', 'views/brand_vendor.xml', + 'views/promotion_program.xml', + 'views/promotion_program_line.xml', + 'views/promotion_program_free_item.xml', + 'views/promotion_program_keyword.xml', 'views/requisition.xml', 'views/landedcost.xml', 'views/product_sla.xml', diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 8b3296a5..929fc8ba 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -15,6 +15,10 @@ from . import product_pricelist from . import product_public_category from . import product_spec from . import product_template +from . import promotion_program +from . import promotion_program_line +from . import promotion_program_free_item +from . import promotion_program_keyword from . import purchase_order_line from . import purchase_order from . import purchase_outstanding diff --git a/indoteknik_custom/models/ir_attachment.py b/indoteknik_custom/models/ir_attachment.py index fd86ab1b..6417fa3f 100644 --- a/indoteknik_custom/models/ir_attachment.py +++ b/indoteknik_custom/models/ir_attachment.py @@ -22,4 +22,9 @@ class Attachment(models.Model): def api_image(self, model, field, id): base_url = self.env['ir.config_parameter'].get_param('web.base.url') is_found = self.is_found(model, field, id) + return base_url + 'api/image/' + model + '/' + field + '/' + str(id) if is_found else '' + + def api_image_local(self, model, field, id): + base_url = self.env['ir.config_parameter'].get_param('web.base.local_url') + is_found = self.is_found(model, field, id) return base_url + 'api/image/' + model + '/' + field + '/' + str(id) if is_found else ''
\ No newline at end of file diff --git a/indoteknik_custom/models/promotion_program.py b/indoteknik_custom/models/promotion_program.py new file mode 100644 index 00000000..e60f48e1 --- /dev/null +++ b/indoteknik_custom/models/promotion_program.py @@ -0,0 +1,19 @@ +from odoo import fields, models + + +class PromotionProgram(models.Model): + _name = "promotion.program" + + name = fields.Char(string="Name") + banner = fields.Binary(string="Banner") + icon = fields.Binary(string="Icon", help="Image 1:1 ratio") + icon_top = fields.Binary(string="Icon Top", help="Icon ini ditampilkan sebagai atribut pada atas gambar di product card pada website") + icon_bottom = fields.Binary(string="Icon Bottom", help="Icon ini ditampilkan sebagai atribut pada bawah gambar di product card pada website") + start_time = fields.Datetime(string="Start Time") + end_time = fields.Datetime(string="End Time") + applies_to = fields.Selection(selection=[ + ("all_user", "All User"), + ("login_user", "Login User") + ]) + program_line = fields.One2many(comodel_name="promotion.program.line", inverse_name="program_id", string="Program Line") + keywords = fields.One2many(comodel_name="promotion.program.keyword", inverse_name="program_id", string="Keywords") diff --git a/indoteknik_custom/models/promotion_program_free_item.py b/indoteknik_custom/models/promotion_program_free_item.py new file mode 100644 index 00000000..705456dd --- /dev/null +++ b/indoteknik_custom/models/promotion_program_free_item.py @@ -0,0 +1,10 @@ +from odoo import fields, models + + +class PromotionProgramFreeItem(models.Model): + _name = "promotion.program.free_item" + _rec_name = "product_id" + + product_id = fields.Many2one(comodel_name="product.product", string="Product Variant") + qty = fields.Integer(string="Qty") + line_id = fields.Many2one(comodel_name="promotion.program.line", string="Program Line") diff --git a/indoteknik_custom/models/promotion_program_keyword.py b/indoteknik_custom/models/promotion_program_keyword.py new file mode 100644 index 00000000..79b938e2 --- /dev/null +++ b/indoteknik_custom/models/promotion_program_keyword.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class PromotionProgramKeyword(models.Model): + _name = "promotion.program.keyword" + + name = fields.Char(string="Keyword") + program_id = fields.Many2one(comodel_name="promotion.program") diff --git a/indoteknik_custom/models/promotion_program_line.py b/indoteknik_custom/models/promotion_program_line.py new file mode 100644 index 00000000..077f7e12 --- /dev/null +++ b/indoteknik_custom/models/promotion_program_line.py @@ -0,0 +1,150 @@ +from odoo import fields, models, api +from datetime import datetime + + +class PromotionProgramLine(models.Model): + _name = "promotion.program.line" + + name = fields.Char(string="Name") + image = fields.Binary(string="Image") + product_id = fields.Many2one(comodel_name="product.product", string="Product Variant") + program_id = fields.Many2one(comodel_name="promotion.program", string="Program") + discount_type = fields.Selection(selection=[ + ("percentage", "Percentage"), + ("fixed_price", "Fixed Price"), + ], string="Discount Type") + discount_amount = fields.Float(string="Discount Amount") + promotion_type = fields.Selection(selection=[ + ("special_price", "Special Price"), + ("bundling", "Bundling"), + ("discount_loading", "Discount Loading"), + ("merchandise", "Merchandise") + ], string="Promotion Type") + minimum_purchase_qty = fields.Integer(string="Minimum Purchase Qty", help="Minimum Qty to applied discount loading") + applies_multiply = fields.Boolean(string="Applies Multiply", help="Is applies multiply") + limit_qty = fields.Integer(string="Limit Qty", help="Limit Qty product in promotion") + limit_qty_user = fields.Integer(string="Limit Qty / User", help="Limit Qty per User") + limit_qty_transaction = fields.Integer(string="Limit Qty / Transaction", help="Limit Qty per Transaction") + line_free_item = fields.One2many(comodel_name="promotion.program.free_item", inverse_name="line_id", string="Line Free Item") + display_on_homepage = fields.Boolean(string="Display on Homepage") + order_line_ids = fields.One2many('sale.order.line', 'program_line_id') + + @api.onchange('product_id') + def _onchange_product_id(self): + if self.product_id and not self.name: + self.name = self.product_id.display_name + self._discount_loading_auto() + + @api.onchange('promotion_type') + def onchange_promotion_type(self): + self._discount_loading_auto() + + def _discount_loading_auto(self): + program_line = self + product = program_line.product_id + promotion_type = program_line.promotion_type + + if promotion_type != 'discount_loading' or not product: + return + + line = self.browse(self.ids) + line.product_id = self.product_id.id + line.promotion_type = self.promotion_type + + product_added = False + line_free_item = program_line.line_free_item + for line in line_free_item: + if line.product_id.id == product.id: + product_added = True + continue + line.unlink() + if not product_added: + data = {'product_id': product.id, + 'qty': 1, 'line_id': program_line.id} + line_free_item.create(data) + + def calculate_price(self, price): + initial_price = price['price'] + if self.discount_type == 'percentage': + price['discount_percentage'] = self.discount_amount + price['price_discount'] = initial_price - (initial_price * self.discount_amount / 100) + elif self.discount_type == 'fixed_price': + price['price_discount'] = self.discount_amount + price['discount_percentage'] = round((initial_price - self.discount_amount) / initial_price * 100) + return price + + def get_active_promotions(self, product_id): + current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + return self.search([ + ('program_id.start_time', '<=', current_time), + ('program_id.end_time', '>=', current_time), + ('product_id', '=', product_id) + ]) + + def _get_remaining_qty(self, user): + remaining_qty = { + 'all': self.limit_qty, + 'user': self.limit_qty_user, + 'transaction': self.limit_qty_transaction + } + for order in self.order_line_ids: + parent_order = order.order_id + if parent_order.state != 'cancel': + remaining_qty['all'] -= order.product_uom_qty + if user and parent_order.partner_id.id == user['partner_id']: + remaining_qty['user'] -= order.product_uom_qty + + if remaining_qty['all'] < remaining_qty['user']: + remaining_qty['user'] = remaining_qty['all'] + if remaining_qty['user'] < remaining_qty['transaction']: + remaining_qty['transaction'] = remaining_qty['user'] + + return remaining_qty + + def _get_remaining_time(self): + calculate_time = self.program_id.end_time - datetime.now() + return round(calculate_time.total_seconds()) + + def _res_limit_qty(self): + return { + 'all': self.limit_qty, + 'user': self.limit_qty_user, + 'transaction': self.limit_qty_transaction, + } + + def _res_promotion_type(self): + return { + 'value': self.promotion_type, + 'label': dict(self._fields['promotion_type'].selection).get(self.promotion_type) + } + + def format(self, user = None): + ir_attachment = self.env['ir.attachment'] + product_price = self.product_id.calculate_website_price() + limit_qty = self._res_limit_qty() + remaining_qty = self._get_remaining_qty(user) + percent_remaining = 0 + if limit_qty['all'] > 0: + percent_remaining = (limit_qty['all'] - remaining_qty['all']) / limit_qty['all'] * 100 + return { + 'id': self.id, + 'name': self.name, + 'image': ir_attachment.api_image('promotion.program.line', 'image', self.id), + 'minimum_purchase_qty': self.minimum_purchase_qty, + 'applies_multiply': self.applies_multiply, + 'remaining_time': self._get_remaining_time(), + 'type': self._res_promotion_type(), + 'limit_qty': limit_qty, + 'remaining_qty': remaining_qty, + 'used_percentage': percent_remaining, + 'price': self.calculate_price(price=product_price) + } + + def res_format(self, user): + data = [x.format(user) for x in self] + return data + + def res_format_cart(self, user): + data = self.format(user) + return data + diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 3d0dfc17..deea64a1 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -88,6 +88,7 @@ class SaleOrder(models.Model): npwp = fields.Char(string="NPWP") purchase_total = fields.Monetary(string='Purchase Total', compute='_compute_purchase_total') voucher_id = fields.Many2one(comodel_name='voucher', string='Voucher') + amount_voucher_disc = fields.Float(string='Voucher Discount') def _compute_purchase_total(self): for order in self: @@ -589,6 +590,8 @@ class SaleOrderLine(models.Model): line_no = fields.Integer('No', default=0, copy=False) note_procurement = fields.Char(string='Note', help="Harap diisi jika ada keterangan tambahan dari Procurement, agar dapat dimonitoring") vendor_subtotal = fields.Float(string='Vendor Subtotal', compute="_compute_vendor_subtotal") + amount_voucher_disc = fields.Float(string='Voucher Discount') + program_line_id = fields.Many2one('promotion.program.line', 'Program Line') def _compute_vendor_subtotal(self): for line in self: diff --git a/indoteknik_custom/models/voucher.py b/indoteknik_custom/models/voucher.py index b327df6d..b3a55499 100644 --- a/indoteknik_custom/models/voucher.py +++ b/indoteknik_custom/models/voucher.py @@ -1,5 +1,6 @@ -from odoo import models, fields +from odoo import models, fields, api from datetime import datetime, timedelta +from odoo.exceptions import ValidationError class Voucher(models.Model): @@ -37,6 +38,15 @@ class Voucher(models.Model): min_purchase_amount = fields.Integer(string='Min. Purchase Amount', help='Nominal minimum untuk dapat menggunakan voucher. Isi 0 jika tidak ada minimum purchase amount') max_discount_amount = fields.Integer(string='Max. Discount Amount', help='Max nominal terhadap persentase diskon') order_ids = fields.One2many('sale.order', 'voucher_id', string='Order') + limit = fields.Integer(string='Limit', help='Voucher limit by sale order. Masukan 0 untuk tanpa limit') + manufacture_ids = fields.Many2many('x_manufactures', string='Brands', help='Voucher appplied only for brand') + excl_pricelist_ids = fields.Many2many('product.pricelist', string='Excluded Pricelists', help='Hide voucher from selected exclude pricelist') + + @api.constrains('description') + def _check_description_length(self): + for record in self: + if record.description and len(record.description) > 120: + raise ValidationError('Description cannot exceed 120 characters') def _compute_display_name(self): for voucher in self: @@ -61,6 +71,9 @@ class Voucher(models.Model): 'remaining_time': self._res_remaining_time(), 'min_purchase_amount': self.min_purchase_amount, 'max_discount_amount': max_discount_amount, + 'manufacture_names': ", ".join([x.x_name for x in self.manufacture_ids]), + 'manufacture_ids': [x.id for x in self.manufacture_ids], + 'excl_pricelist_ids': [x.id for x in self.excl_pricelist_ids], } return data diff --git a/indoteknik_custom/models/website_user_cart.py b/indoteknik_custom/models/website_user_cart.py index 8046469f..29bf4291 100644 --- a/indoteknik_custom/models/website_user_cart.py +++ b/indoteknik_custom/models/website_user_cart.py @@ -5,6 +5,70 @@ class WebsiteUserCart(models.Model): _name = 'website.user.cart' _rec_name = 'user_id' - user_id = fields.Many2one('res.users', string='User', help="User ID yang terdaftar di table res.users") - product_id = fields.Many2one('product.product', string='Product', help="Product yang terdaftar di table product.product") + user_id = fields.Many2one('res.users', string='User') + product_id = fields.Many2one('product.product', string='Product') + program_line_id = fields.Many2one('promotion.program.line', string='Program', help="Apply program") qty = fields.Float(string='Quantity', digits='Product Unit of Measure') + is_selected = fields.Boolean(string='Selected?', digits='Is selected to process checkout') + + def get_product(self): + user_data = { + 'partner_id': self.user_id.partner_id.id, + 'user_id': self.user_id.id + } + product = self.product_id.v2_api_single_response(self.product_id) + product['cart_id'] = self.id + product['quantity'] = self.qty + product['subtotal'] = self.qty * product['price']['price_discount'] + product['selected'] = self.is_selected + product['program'] = None + product['can_buy'] = True + if self.program_line_id: + product['program'] = self.program_line_id.res_format_cart(user=user_data, quantity=self.qty) + + if product['program']: + if self.qty < product['program']['minimum_purchase_qty'] or self.qty > product['program']['remaining_qty']['transaction']: + product['can_buy'] = False + product['price'] = product['program']['price'] + + return product + + def get_products(self): + return [x.get_product() for x in self] + + def get_product_by_user(self, user_id, selected = False): + user_id = int(user_id) + parameters = [('user_id', '=', user_id)] + if selected: + parameters.append(('is_selected', '=', True)) + carts = self.search(parameters) + products = carts.get_products() + return products + + def get_user_checkout(self, user_id, voucher=False): + products = self.get_product_by_user(user_id=user_id, selected=True) + total_purchase = sum(x['price']['price'] * x['quantity'] for x in products) + total_discount = sum((x['price']['price'] - x['price']['price_discount']) * x['quantity'] for x in products) + subtotal = total_purchase - total_discount + discount_voucher = 0 + if voucher: + discount_voucher = voucher.calculate_discount(subtotal) + subtotal -= discount_voucher + tax = round(subtotal * 0.11) + grand_total = subtotal + tax + total_weight = sum(x['weight'] * x['quantity'] for x in products) + result = { + 'total_purchase': total_purchase, + 'total_discount': total_discount, + 'discount_voucher': discount_voucher, + 'subtotal': subtotal, + 'tax': tax, + 'grand_total': round(grand_total), + 'total_weight': { + 'kg': total_weight, + 'g': total_weight * 1000 + }, + 'has_product_without_weight': any(not product.get('weight') or product.get('weight') == 0 for product in products), + 'products': products + } + return result
\ No newline at end of file diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 379c4c8e..f115da56 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -51,6 +51,10 @@ access_group_partner,access.group.partner,model_group_partner,,1,1,1,1 access_procurement_monitoring_detail,access.procurement.monitoring.detail,model_procurement_monitoring_detail,,1,1,1,1 access_rajaongkir_kurir,access.rajaongkir.kurir,model_rajaongkir_kurir,,1,1,1,1 access_brand_vendor,access.brand.vendor,model_brand_vendor,,1,1,1,1 +access_promotion_program,access.promotion.program,model_promotion_program,,1,1,1,1 +access_promotion_program_line,access.promotion.program.line,model_promotion_program_line,,1,1,1,1 +access_promotion_program_free_item,access.promotion.program.free_item,model_promotion_program_free_item,,1,1,1,1 +access_promotion_program_keyword,access.promotion.program.keyword,model_promotion_program_keyword,,1,1,1,1 access_requisition,access.requisition,model_requisition,,1,1,1,1 access_requisition_line,access.requisition.line,model_requisition_line,,1,1,1,1 access_requisition_purchase_match,access.requisition.purchase.match,model_requisition_purchase_match,,1,1,1,1 diff --git a/indoteknik_custom/views/promotion_program.xml b/indoteknik_custom/views/promotion_program.xml new file mode 100644 index 00000000..5dff80e6 --- /dev/null +++ b/indoteknik_custom/views/promotion_program.xml @@ -0,0 +1,73 @@ +<odoo> + <record id="promotion_program_tree" model="ir.ui.view"> + <field name="name">Promotion Program Tree</field> + <field name="model">promotion.program</field> + <field name="arch" type="xml"> + <tree> + <field name="name" /> + <field name="start_time" /> + <field name="end_time" /> + <field name="applies_to" /> + </tree> + </field> + </record> + + <record id="promotion_program_form" model="ir.ui.view"> + <field name="name">Promotion Program Form</field> + <field name="model">promotion.program</field> + <field name="arch" type="xml"> + <form> + <sheet> + <group> + <group> + <field name="name" /> + <field name="keywords" widget="many2many_tags" /> + <field name="banner" widget="image" height="160" /> + </group> + <group> + <field name="start_time" /> + <field name="end_time" /> + <field name="applies_to" /> + </group> + </group> + <notebook> + <page string="Program Lines" name="program_lines"> + <field name="program_line" /> + </page> + <page string="Image Properties" name="image_properties"> + <group> + <group> + <field name="icon" widget="image" height="120" /> + <field name="icon_top" widget="image" height="80" /> + <field name="icon_bottom" widget="image" height="80" /> + </group> + </group> + </page> + </notebook> + </sheet> + </form> + </field> + </record> + + <record id="promotion_program_action" model="ir.actions.act_window"> + <field name="name">Promotion Program</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">promotion.program</field> + <field name="view_mode">tree,form</field> + </record> + + <menuitem + id="menu_promotion_program_parent" + name="Promotion Program" + parent="website.menu_website_configuration" + sequence="7" + /> + + <menuitem + id="menu_promotion_program" + name="Program" + parent="indoteknik_custom.menu_promotion_program_parent" + sequence="1" + action="promotion_program_action" + /> +</odoo>
\ No newline at end of file diff --git a/indoteknik_custom/views/promotion_program_free_item.xml b/indoteknik_custom/views/promotion_program_free_item.xml new file mode 100644 index 00000000..52d421fe --- /dev/null +++ b/indoteknik_custom/views/promotion_program_free_item.xml @@ -0,0 +1,44 @@ +<odoo> + <record id="promotion_program_free_item_tree" model="ir.ui.view"> + <field name="name">Promotion Program Free Item Tree</field> + <field name="model">promotion.program.free_item</field> + <field name="arch" type="xml"> + <tree> + <field name="product_id" /> + <field name="qty" /> + </tree> + </field> + </record> + + <record id="promotion_program_free_item_form" model="ir.ui.view"> + <field name="name">Promotion Program Free Item Form</field> + <field name="model">promotion.program.free_item</field> + <field name="arch" type="xml"> + <form> + <sheet> + <group> + <group> + <field name="product_id" /> + <field name="qty" /> + </group> + </group> + </sheet> + </form> + </field> + </record> + + <record id="promotion_program_free_item_action" model="ir.actions.act_window"> + <field name="name">Promotion Program Free Item</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">promotion.program.free_item</field> + <field name="view_mode">tree,form</field> + </record> + + <menuitem + id="menu_promotion_program_free_item" + name="Program Free Item" + parent="indoteknik_custom.menu_promotion_program_parent" + sequence="3" + action="promotion_program_free_item_action" + /> +</odoo>
\ No newline at end of file diff --git a/indoteknik_custom/views/promotion_program_keyword.xml b/indoteknik_custom/views/promotion_program_keyword.xml new file mode 100644 index 00000000..fd279665 --- /dev/null +++ b/indoteknik_custom/views/promotion_program_keyword.xml @@ -0,0 +1,44 @@ +<odoo> + <record id="promotion_program_keyword_tree" model="ir.ui.view"> + <field name="name">Promotion Program Keyword Tree</field> + <field name="model">promotion.program.keyword</field> + <field name="arch" type="xml"> + <tree> + <field name="name" /> + <field name="program_id" /> + </tree> + </field> + </record> + + <record id="promotion_program_keyword_form" model="ir.ui.view"> + <field name="name">Promotion Program Keyword Form</field> + <field name="model">promotion.program.keyword</field> + <field name="arch" type="xml"> + <form> + <sheet> + <group> + <group> + <field name="name" /> + <field name="program_id" /> + </group> + </group> + </sheet> + </form> + </field> + </record> + + <record id="promotion_program_keyword_action" model="ir.actions.act_window"> + <field name="name">Promotion Program Keyword</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">promotion.program.keyword</field> + <field name="view_mode">tree,form</field> + </record> + + <menuitem + id="menu_promotion_program_keyword" + name="Program Keyword" + parent="indoteknik_custom.menu_promotion_program_parent" + sequence="4" + action="promotion_program_keyword_action" + /> +</odoo>
\ No newline at end of file diff --git a/indoteknik_custom/views/promotion_program_line.xml b/indoteknik_custom/views/promotion_program_line.xml new file mode 100644 index 00000000..5963d0dd --- /dev/null +++ b/indoteknik_custom/views/promotion_program_line.xml @@ -0,0 +1,98 @@ +<odoo> + <record id="promotion_program_line_tree" model="ir.ui.view"> + <field name="name">Promotion Program Line Tree</field> + <field name="model">promotion.program.line</field> + <field name="arch" type="xml"> + <tree> + <field name="name" /> + <field name="promotion_type" /> + <field name="limit_qty" /> + <field name="limit_qty_user" /> + <field name="limit_qty_transaction" /> + <field name="line_free_item" /> + <field name="display_on_homepage" /> + </tree> + </field> + </record> + + <record id="promotion_program_line_form" model="ir.ui.view"> + <field name="name">Promotion Program Line Form</field> + <field name="model">promotion.program.line</field> + <field name="arch" type="xml"> + <form> + <sheet> + <group> + <group> + <field name="name" /> + <field name="promotion_type"/> + <field name="product_id" /> + <field name="image" widget="image" height="160" /> + </group> + <group> + <field name="limit_qty" /> + <field name="limit_qty_user" /> + <field name="limit_qty_transaction" /> + <field + name="discount_type" + attrs="{'invisible': [('promotion_type', '!=', 'special_price')]}" + /> + <field + name="discount_amount" + attrs="{'invisible': [('promotion_type', '!=', 'special_price')]}" + /> + <field + name="minimum_purchase_qty" + attrs="{'invisible': [('promotion_type', 'not in', ['discount_loading', 'bundling', 'merchandise'])]}" + /> + <field + name="applies_multiply" + attrs="{'invisible': [('promotion_type', 'not in', ['discount_loading', 'bundling', 'merchandise'])]}" + /> + <field + name="display_on_homepage" + attrs="{'invisible': [('promotion_type', '!=', 'special_price')]}" + /> + </group> + </group> + <notebook> + <page + string="Line Free Item" + name="line_free_items" + attrs="{'invisible': [('promotion_type', '=', 'special_price')]}" + > + <field name="line_free_item" /> + </page> + <page + string="Order Line" + name="order_line" + > + <field name="order_line_ids" readonly="1"> + <tree> + <field name="order_id" /> + <field name="name" /> + <field name="product_id" /> + <field name="product_uom_qty" /> + </tree> + </field> + </page> + </notebook> + </sheet> + </form> + </field> + </record> + + <record id="promotion_program_line_action" model="ir.actions.act_window"> + <field name="name">Promotion Program Line</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">promotion.program.line</field> + <field name="view_mode">tree,form</field> + </record> + + <menuitem + id="menu_promotion_program_line" + name="Program Line" + parent="indoteknik_custom.menu_promotion_program_parent" + sequence="2" + action="promotion_program_line_action" + /> +</odoo>
\ No newline at end of file diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index b55fefff..4570f43b 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -26,7 +26,7 @@ <field name="delivery_amt"/> <field name="fee_third_party"/> <field name="total_percent_margin"/> - <field name="voucher_id" /> + <field name="voucher_id" readonly="1" /> </field> <field name="analytic_account_id" position="after"> <field name="customer_type" attrs="{'required': ['|', ('create_date', '>', '2023-06-28'), ('create_date', '=', False)]}"/> @@ -71,12 +71,19 @@ <field name="item_percent_margin"/> <field name="note_procurement" optional="hide"/> <field name="vendor_subtotal" optional="hide"/> + <field name="amount_voucher_disc" string="Voucher" optional="hide"/> + <field name="program_line_id" optional="1" /> </xpath> <xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='product_id']" position="before"> <field name="line_no" readonly="1" optional="hide"/> </xpath> <field name="amount_total" position="after"> <field name="grand_total"/> + <label for="amount_voucher_disc" string="Voucher" /> + <div> + <field class="mb-0" name="amount_voucher_disc" string="Voucher"/> + <div class="text-right mb-2"><small>*Hanya informasi</small></div> + </div> <field name="total_margin"/> <field name="total_percent_margin"/> </field> diff --git a/indoteknik_custom/views/voucher.xml b/indoteknik_custom/views/voucher.xml index cd42586e..c6741a8d 100755 --- a/indoteknik_custom/views/voucher.xml +++ b/indoteknik_custom/views/voucher.xml @@ -28,15 +28,7 @@ <group> <field name="image" widget="image" width="120"/> <field name="name" required="1" /> - <field name="description" placeholder="Insert short description..." /> - </group> - <group string="Rules"> - <field name="code" required="1" /> - <field name="visibility" required="1" /> - <field name="start_time" required="1"/> - <field name="end_time" required="1"/> </group> - <group></group> <group string="Discount Settings"> <field name="min_purchase_amount" widget="monetary" required="1" /> <field name="discount_type" required="1" /> @@ -60,12 +52,27 @@ <field name="max_discount_amount" widget="monetary" required="1" attrs="{'invisible': [('discount_type', '!=', 'percentage')]}"/> </group> + <group string="Rules"> + <field name="code" required="1" /> + <field name="visibility" required="1" /> + <field name="start_time" required="1"/> + <field name="end_time" required="1"/> + <field name="limit" required="1"/> + <field name="manufacture_ids" widget="many2many_tags"/> + <field name="excl_pricelist_ids" widget="many2many_tags"/> + </group> </group> <notebook> + <page name="description" string="Description"> + <label for="description" string="Max 120 characters:" class="font-weight-normal mb-2 oe_edit_only"/> + <field name="description" placeholder="Insert short description..." /> + </page> <page name="order_page" string="Orders"> <field name="order_ids" readonly="1"> <tree> <field name="name" /> + <field name="partner_id" /> + <field name="amount_voucher_disc" /> <field name="amount_total" /> </tree> </field> diff --git a/indoteknik_custom/views/website_user_cart.xml b/indoteknik_custom/views/website_user_cart.xml index 890d801c..fbd08acb 100755 --- a/indoteknik_custom/views/website_user_cart.xml +++ b/indoteknik_custom/views/website_user_cart.xml @@ -13,7 +13,9 @@ <tree> <field name="user_id"/> <field name="product_id"/> + <field name="program_line_id"/> <field name="qty"/> + <field name="is_selected"/> </tree> </field> </record> @@ -28,7 +30,9 @@ <group> <field name="user_id"/> <field name="product_id"/> + <field name="program_line_id" domain="[('product_id', '=', product_id)]"/> <field name="qty"/> + <field name="is_selected"/> </group> <group></group> </group> |
