summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIT Fixcomart <it@fixcomart.co.id>2023-07-24 08:38:12 +0000
committerIT Fixcomart <it@fixcomart.co.id>2023-07-24 08:38:12 +0000
commitc344ccd81208b5b466ae047dbb9e084dd5d0f358 (patch)
treec07caf6a1df48719b828fbe270bb62ccc3e0c947
parent989002aca3ed9b1e724e9b98d8ca3513ba598663 (diff)
parent2ad6b4ecf783b5dfe0d4aa11f238cbf6aefd9747 (diff)
Merged in production (pull request #66)
Production
-rw-r--r--indoteknik_api/controllers/api_v1/cart.py53
-rw-r--r--indoteknik_api/controllers/api_v1/product.py31
-rw-r--r--indoteknik_api/controllers/api_v1/product_variant.py12
-rw-r--r--indoteknik_api/controllers/api_v1/promotion.py70
-rw-r--r--indoteknik_api/controllers/api_v1/sale_order.py132
-rw-r--r--indoteknik_api/controllers/api_v1/voucher.py69
-rw-r--r--indoteknik_api/controllers/api_v2/product.py4
-rw-r--r--indoteknik_api/controllers/api_v2/product_variant.py4
-rw-r--r--indoteknik_api/controllers/controller.py1
-rw-r--r--indoteknik_api/models/product_product.py71
-rw-r--r--indoteknik_api/models/product_template.py6
-rw-r--r--indoteknik_api/models/res_users.py1
-rwxr-xr-xindoteknik_custom/__manifest__.py5
-rwxr-xr-xindoteknik_custom/models/__init__.py4
-rw-r--r--indoteknik_custom/models/account_move.py14
-rw-r--r--indoteknik_custom/models/account_move_due_extension.py21
-rw-r--r--indoteknik_custom/models/bill_receipt.py182
-rw-r--r--indoteknik_custom/models/ir_attachment.py5
-rw-r--r--indoteknik_custom/models/product_sla.py9
-rwxr-xr-xindoteknik_custom/models/product_template.py2
-rw-r--r--indoteknik_custom/models/promotion_program.py19
-rw-r--r--indoteknik_custom/models/promotion_program_free_item.py10
-rw-r--r--indoteknik_custom/models/promotion_program_keyword.py8
-rw-r--r--indoteknik_custom/models/promotion_program_line.py150
-rwxr-xr-xindoteknik_custom/models/sale_order.py11
-rwxr-xr-xindoteknik_custom/models/stock_vendor.py1
-rw-r--r--indoteknik_custom/models/voucher.py15
-rw-r--r--indoteknik_custom/models/website_user_cart.py68
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv4
-rw-r--r--indoteknik_custom/views/account_move.xml16
-rw-r--r--indoteknik_custom/views/bill_receipt.xml78
-rw-r--r--indoteknik_custom/views/promotion_program.xml73
-rw-r--r--indoteknik_custom/views/promotion_program_free_item.xml44
-rw-r--r--indoteknik_custom/views/promotion_program_keyword.xml44
-rw-r--r--indoteknik_custom/views/promotion_program_line.xml98
-rwxr-xr-xindoteknik_custom/views/sale_order.xml3
-rwxr-xr-xindoteknik_custom/views/voucher.xml23
-rwxr-xr-xindoteknik_custom/views/website_user_cart.xml4
38 files changed, 1090 insertions, 275 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.py b/indoteknik_api/controllers/api_v1/product.py
index 8803bae3..7ec6459b 100644
--- a/indoteknik_api/controllers/api_v1/product.py
+++ b/indoteknik_api/controllers/api_v1/product.py
@@ -16,31 +16,40 @@ class Product(controller.Controller):
@controller.Controller.must_authorized()
def get_product_template_stock_by_id(self, **kw):
id = int(kw.get('id'))
+ date_7_days_ago = datetime.now() - timedelta(days=7)
product = request.env['product.product'].search([('id', '=', id)], limit=1)
product_sla = request.env['product.sla'].search([('product_variant_id', '=', id)], limit=1)
-
+ stock_vendor = request.env['stock.vendor'].search([
+ ('product_variant_id', '=', id),
+ ('write_date', '>=', date_7_days_ago.strftime("%Y-%m-%d %H:%M:%S"))
+ ], limit=1)
+
qty_available = product.qty_onhand_bandengan
- qty_available -= 10
- if qty_available < 10:
- qty_available = 0
qty = 0
sla_date = '-'
-
+
is_altama_product = product.x_manufacture.id in [10,122,89]
if is_altama_product:
try:
+ # Qty Altama
qty_altama = request.env['product.template'].get_stock_altama(product.default_code)
qty_altama -= int(qty_altama * 0.1)
qty_altama = math.ceil(float(qty_altama))
- qty = qty_altama
- if qty_available > 10:
- qty += qty_available
+ total_adem = qty_altama
+ # Qty Stock Vendor
+ qty_vendor = stock_vendor.quantity
+ qty_vendor -= int(qty_vendor * 0.1)
+ qty_vendor = math.ceil(float(qty_vendor))
+ total_excell = qty_vendor
+ if qty_available > 0:
+ qty = qty_available + total_adem + total_excell
sla_date = '1 Hari'
- elif qty_altama > 0:
+ elif qty_altama > 0 or qty_vendor > 0:
+ qty = total_adem if qty_altama > 0 else total_excell
sla_date = '2-4 Hari'
- else:
- sla_date = 'Indent'
+ else:
+ sla_date = '3-7 Hari'
except:
print('error')
else:
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 713b3bff..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,71 +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:
+ 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)
sale_order.voucher_id = voucher.id
sale_order.amount_voucher_disc = voucher_discount
- total_qty = sum(line.product_uom_qty for line in sale_order.order_line)
- voucher_discount_item = voucher_discount / total_qty
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
- line.amount_voucher_disc = voucher_discount_item * line.product_uom_qty
+ 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_api/models/res_users.py b/indoteknik_api/models/res_users.py
index 69981e41..0fe9008b 100644
--- a/indoteknik_api/models/res_users.py
+++ b/indoteknik_api/models/res_users.py
@@ -20,6 +20,7 @@ class ResUsers(models.Model):
data = {
'id': res_user.id,
'parent_id': res_user.parent_id.id or False,
+ 'parent_name': res_user.parent_id.name or False,
'partner_id': res_user.partner_id.id,
'name': res_user.name,
'email': res_user.login,
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py
index f91b33d7..ead47bd3 100755
--- a/indoteknik_custom/__manifest__.py
+++ b/indoteknik_custom/__manifest__.py
@@ -75,11 +75,16 @@
'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',
'views/voucher.xml',
'views/bill_receipt.xml',
+ # 'views/account_bills.xml',
'report/report.xml',
'report/report_banner_banner.xml',
'report/report_banner_banner2.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/account_move.py b/indoteknik_custom/models/account_move.py
index 99439915..33fb73b5 100644
--- a/indoteknik_custom/models/account_move.py
+++ b/indoteknik_custom/models/account_move.py
@@ -1,6 +1,9 @@
from odoo import models, api, fields
from odoo.exceptions import AccessError, UserError, ValidationError
from datetime import timedelta, date
+import logging
+
+_logger = logging.getLogger(__name__)
class AccountMove(models.Model):
@@ -12,9 +15,19 @@ class AccountMove(models.Model):
date_kirim_tukar_faktur = fields.Date(string='Kirim Faktur')
resi_tukar_faktur = fields.Char(string='Resi Faktur')
date_terima_tukar_faktur = fields.Date(string='Terima Faktur')
+ payment_schedule = fields.Date(string='Jadwal Pembayaran')
shipper_faktur_id = fields.Many2one('delivery.carrier', string='Shipper Faktur')
due_extension = fields.Integer(string='Due Extension', default=0)
new_due_date = fields.Date(string='New Due')
+ counter = fields.Integer(string="Counter", default=0)
+ due_line = fields.One2many('due.extension.line', 'invoice_id', compute='_compute_due_line', string='Due Extension Lines')
+
+ def _compute_due_line(self):
+ for invoice in self:
+ invoice.due_line = self.env['due.extension.line'].search([
+ ('invoice_id', '=', invoice.id),
+ ('due_id.approval_status', '=', 'approved')
+ ])
def unlink(self):
res = super(AccountMove, self).unlink()
@@ -102,3 +115,4 @@ class AccountMove(models.Model):
add_days += line.days
due_date = tukar_date + timedelta(days=add_days)
invoice.invoice_date_due = due_date
+ \ No newline at end of file
diff --git a/indoteknik_custom/models/account_move_due_extension.py b/indoteknik_custom/models/account_move_due_extension.py
index 27c8577d..43207534 100644
--- a/indoteknik_custom/models/account_move_due_extension.py
+++ b/indoteknik_custom/models/account_move_due_extension.py
@@ -120,7 +120,10 @@ class DueExtension(models.Model):
'open_amt': invoice.amount_residual_signed
}])
count += 1
+ invoice.counter+=1
+
_logger.info("Due Extension Line generated %s" % count)
+
def unlink(self):
res = super(DueExtension, self).unlink()
@@ -135,6 +138,12 @@ class DueExtensionLine(models.Model):
_order = 'due_id, id'
due_id = fields.Many2one('due.extension', string='Due Ref', required=True, ondelete='cascade', index=True, copy=False)
+ due_description = fields.Text(string="Description", compute="_compute_due_description")
+ due_approval_status = fields.Selection([
+ ('pengajuan', 'Pengajuan'),
+ ('approved', 'Approved'),
+ ], string="Approval Status", compute="_compute_due_approval_status")
+ due_day_extension = fields.Char(string="Day Extension", compute="_compute_due_day_extension")
partner_id = fields.Many2one('res.partner', string='Customer')
invoice_id = fields.Many2one('account.move', string='Invoice')
date_invoice = fields.Date(string='Invoice Date')
@@ -145,6 +154,18 @@ class DueExtensionLine(models.Model):
due_date = fields.Date(string='Due Date', compute="_compute_due_date")
day_to_due = fields.Integer(string='Day To Due', compute="_compute_day_to_due")
+ def _compute_due_description(self):
+ for line in self:
+ line.due_description = line.due_id.description
+
+ def _compute_due_approval_status(self):
+ for line in self:
+ line.due_approval_status = line.due_id.approval_status
+
+ def _compute_due_day_extension(self):
+ for line in self:
+ line.due_day_extension = line.due_id.day_extension
+
def _compute_day_to_due(self):
for line in self:
line.day_to_due = line.invoice_id.invoice_day_to_due
diff --git a/indoteknik_custom/models/bill_receipt.py b/indoteknik_custom/models/bill_receipt.py
index 806edc25..76449c1f 100644
--- a/indoteknik_custom/models/bill_receipt.py
+++ b/indoteknik_custom/models/bill_receipt.py
@@ -1,104 +1,120 @@
from odoo import models, api, fields
from odoo.exceptions import AccessError, UserError, ValidationError
-from datetime import timedelta, date
+from datetime import timedelta
import logging
_logger = logging.getLogger(__name__)
-class BillReceipt(models.Model):
- _name = "bill.receipt"
- _description = "Bill Receipt"
- _rec_name = 'number'
-
- number = fields.Char(string='Document No', index=True, copy=False, readonly=True, tracking=True)
- vendor_id = fields.Many2one('res.partner', string="Vendor")
- document_date = fields.Date(string="Document Date")
- description = fields.Text(string='Description')
- validated = fields.Boolean(string="Validated", readonly=True)
- bill_line = fields.One2many('bill.receipt.line', 'bill_id', string='Bill Receipt Lines')
- grand_total = fields.Float(string="Grand Total")
- def compute_amt_total_without_service(self):
- for order in self:
- sum_price_total = 0
- for line in order.order_line:
- if line.product_id.type == 'product':
- sum_price_total += line.price_total
- order.amount_total_without_service = sum_price_total
+class BillReceipt(models.Model):
+ _name = 'bill.receipt'
+ _description = 'Bill Receipt'
+ _order = 'bill_date desc, id desc'
+
+ number = fields.Char(string='Document No', index=True, copy=False, readonly=True)
+ bill_date = fields.Date(string='Bill Date', required=True)
+ partner_id = fields.Many2one(
+ 'res.partner', string='Vendor',
+ required=True, change_default=True, index=True, tracking=1)
+ bill_line = fields.One2many('bill.receipt.line', 'bill_id', string='Bill Receipt Lines', auto_join=True)
+ # dunning_level = fields.Integer(string='Bill Level', default=30, help='30 hari sebelum jatuh tempo invoice')
+ date_kirim_tukar_faktur = fields.Date(string='Kirim Faktur')
+ resi_tukar_faktur = fields.Char(string='Resi Faktur')
+ date_terima_tukar_faktur = fields.Date(string='Terima Faktur')
+ shipper_faktur_id = fields.Many2one('delivery.carrier', string='Shipper Faktur')
+ is_validated = fields.Boolean(string='Validated')
+ notification = fields.Char(string='Notification')
+
+ def copy_date_faktur(self):
+ if not self.is_validated:
+ raise UserError('Harus di validate dulu')
+ for line in self.bill_line:
+ invoice = line.invoice_id
+ if not invoice.date_kirim_tukar_faktur and self.date_kirim_tukar_faktur:
+ invoice.date_kirim_tukar_faktur = self.date_kirim_tukar_faktur
+ tukar_date = self.date_kirim_tukar_faktur
+ term = invoice.invoice_payment_term_id
+ add_days = 0
+ for line in term.line_ids:
+ add_days += line.days
+ due_date = tukar_date + timedelta(days=add_days)
+ invoice.invoice_date_due = due_date
+ if not invoice.resi_tukar_faktur:
+ invoice.resi_tukar_faktur = self.resi_tukar_faktur
+ if not invoice.date_terima_tukar_faktur and self.date_terima_tukar_faktur:
+ invoice.date_terima_tukar_faktur = self.date_terima_tukar_faktur
+ tukar_date = self.date_terima_tukar_faktur
+ term = invoice.invoice_payment_term_id
+ add_days = 0
+ for line in term.line_ids:
+ add_days += line.days
+ due_date = tukar_date + timedelta(days=add_days)
+ invoice.invoice_date_due = due_date
+ if not invoice.shipper_faktur_id:
+ invoice.shipper_faktur_id = self.shipper_faktur_id
+ self.notification = 'Berhasil copy tanggal terima faktur ke setiap invoice %s' % self.date_terima_tukar_faktur
+
+ def validate_bill(self):
+ if not self.bill_line:
+ raise UserError('Bill Line masih kosong, generate dulu')
+ else:
+ self.is_validated = True
+ self.notification = 'Jangan lupa klik Copy Date jika sudah ada tanggal kirim / tanggal terima faktur'
+
+ def generate_bill_line(self):
+ if self.is_validated:
+ raise UserError('Sudah di validate, tidak bisa digenerate ulang')
+ if self.bill_line:
+ raise UserError('Harus hapus semua line jika ingin generate ulang')
+ if self.partner_id.parent_id:
+ raise UserError('Harus pilih parent company')
+
+ partners = []
+ partners += self.partner_id.child_ids
+ partners.append(self.partner_id)
+
+ for partner in partners:
+ query = [
+ ('move_type', '=', 'in_invoice'),
+ ('state', '=', 'posted'),
+ ('partner_id', '=', partner.id),
+ ('payment_state', '=', 'not_paid'),
+ ('date_kirim_tukar_faktur', '=', False),
+ ]
+ invoices = self.env['account.move'].search(query, order='invoice_date')
+ count = 0
+ for invoice in invoices:
+ self.env['bill.receipt.line'].create([{
+ 'bill_id': self.id,
+ 'partner_id': invoice.partner_id.id,
+ 'invoice_id': invoice.id,
+ 'date_invoice': invoice.invoice_date,
+ 'reference': invoice.ref,
+ 'total_amt': invoice.amount_total,
+ 'open_amt': invoice.amount_residual_signed,
+ 'due_date': invoice.invoice_date_due
+ }])
+ count += 1
+ _logger.info("Bill Line generated %s" % count)
@api.model
def create(self, vals):
- vals['number'] = self.env['ir.sequence'].next_by_code('due.extension') or '0'
+ vals['number'] = self.env['ir.sequence'].next_by_code('bill.receipt') or '0'
result = super(BillReceipt, self).create(vals)
return result
-
- def validate_button(self):
- if not self.bill_line:
- raise UserError('Bill receipt line masih kosong')
-
- self.validated = True
- def cancel_button(self):
- if not self.bill_line:
- raise UserError('Bill receipt line masih kosong')
-
- if self.validated != True:
- raise UserError('Document ini belum di validate')
- self.validated = False
-
class BillReceiptLine(models.Model):
_name = 'bill.receipt.line'
_description = 'Bill Receipt Line'
_order = 'bill_id, id'
- bill_id = fields.Many2one('bill.receipt', string='Bill Receipt')
- po_id = fields.Many2one('purchase.order', string='PO')
- sale_order_id = fields.Many2one('sale.order', string='Sale Order')
- user_id = fields.Many2one('res.users', string='Purchase Rep')
- payment_term_id = fields.Many2one('account.payment.term', string='Payment Terms')
- vendor_id = fields.Many2one('res.partner', string='Vendor')
- date_approve = fields.Datetime(string='Confirmation Date')
- date_planned = fields.Datetime(string='Receipt Date')
- amount_untaxed = fields.Float(string='Untaxed Amount')
- amount_total = fields.Float(string='Total')
+ bill_id = fields.Many2one('bill.receipt', string='Bill Ref', required=True, ondelete='cascade', index=True, copy=False)
+ partner_id = fields.Many2one('res.partner', string='Customer')
+ invoice_id = fields.Many2one('account.move', string='Invoice')
+ date_invoice = fields.Date(string='Invoice Date')
reference = fields.Char(string='Reference')
- partner_ref = fields.Char(string='Vendor Reference')
-
- @api.onchange('po_id')
- def onchange_partner_ref(self):
- self.partner_ref = self.po_id.partner_ref
-
- @api.onchange('po_id')
- def onchange_vendor_id(self):
- self.vendor_id = self.po_id.partner_id
-
- @api.onchange('po_id')
- def onchange_date_approve(self):
- self.date_approve = self.po_id.date_approve
-
- @api.onchange('po_id')
- def onchange_date_planned(self):
- self.date_planned = self.po_id.date_planned
-
- @api.onchange('po_id')
- def onchange_amount_untaxed(self):
- self.amount_untaxed = self.po_id.amount_untaxed
-
- @api.onchange('po_id')
- def onchange_user_id(self):
- self.user_id = self.po_id.user_id
-
- @api.onchange('po_id')
- def onchange_sale_order_id(self):
- self.sale_order_id = self.po_id.sale_order_id
-
- @api.onchange('po_id')
- def onchange_payment_term_id(self):
- self.payment_term_id = self.po_id.payment_term_id
-
- @api.onchange('po_id')
- def onchange_amount_total(self):
- self.amount_total = self.po_id.amount_total
-
+ total_amt = fields.Float(string='Total Amount')
+ open_amt = fields.Float(string='Open Amount')
+ due_date = fields.Date(string='Due Date')
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/product_sla.py b/indoteknik_custom/models/product_sla.py
index f969502f..2e663d30 100644
--- a/indoteknik_custom/models/product_sla.py
+++ b/indoteknik_custom/models/product_sla.py
@@ -51,12 +51,9 @@ class ProductSla(models.Model):
qty_available = 0
qty_available = product.qty_onhand_bandengan
- qty_available -= 10
- if qty_available < 10:
- qty_available = 0
-
- if qty_available > 10:
+
+ if qty_available > 0:
self.sla = '1 Hari'
query = [
@@ -80,7 +77,7 @@ class ProductSla(models.Model):
rounded_leadtime = math.ceil(avg_leadtime)
self.avg_leadtime = rounded_leadtime
if rounded_leadtime >= 1 and rounded_leadtime <= 5:
- self.sla = '3-6 Hari'
+ self.sla = '3-7 Hari'
elif rounded_leadtime >= 6 and rounded_leadtime <= 10:
self.sla = '4-12 Hari'
elif rounded_leadtime >= 11:
diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py
index 1a83b702..7abdf1c1 100755
--- a/indoteknik_custom/models/product_template.py
+++ b/indoteknik_custom/models/product_template.py
@@ -221,7 +221,7 @@ class ProductTemplate(models.Model):
datas = json.loads(response.text)['data']
qty = 0
for data in datas:
- availability = int(data['availability']) # Mengonversi ke tipe data int
+ availability = float(data['availability']) # Mengonversi ke tipe data int
qty += availability # Mengakumulasi qty dari setiap data
return qty
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 e151bf22..552f1b0a 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -243,6 +243,16 @@ class SaleOrder(models.Model):
self.npwp = parent_id.npwp
self.sppkp = parent_id.sppkp
self.customer_type = parent_id.customer_type
+
+ @api.onchange('partner_id')
+ def onchange_partner_id(self):
+ # INHERIT
+ result = super(SaleOrder, self).onchange_partner_id()
+ parent_id = self.partner_id.parent_id
+ parent_id = parent_id if parent_id else self.partner_id
+
+ self.partner_invoice_id = parent_id
+ return result
def _get_purchases(self):
po_state = ['done', 'draft', 'purchase']
@@ -591,6 +601,7 @@ class SaleOrderLine(models.Model):
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/stock_vendor.py b/indoteknik_custom/models/stock_vendor.py
index 1e5bce16..f214a5e1 100755
--- a/indoteknik_custom/models/stock_vendor.py
+++ b/indoteknik_custom/models/stock_vendor.py
@@ -1,5 +1,6 @@
from odoo import fields, models, api
import logging
+from datetime import datetime, timedelta
_logger = logging.getLogger(__name__)
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/account_move.xml b/indoteknik_custom/views/account_move.xml
index 7f7fb228..0fd6b5b5 100644
--- a/indoteknik_custom/views/account_move.xml
+++ b/indoteknik_custom/views/account_move.xml
@@ -10,13 +10,29 @@
<button name="indoteknik_custom.action_view_invoice_reklas" string="Reklas"
type="action" class="btn-primary" attrs="{'invisible': [('state', '!=', 'posted')]}"/>
</button>
+ <field name="invoice_date" position="after">
+ <field name="payment_schedule" attrs="{'invisible': [('move_type', '!=', 'in_invoice')]}"/>
+ </field>
<field name="invoice_user_id" position="after">
<field name="date_kirim_tukar_faktur"/>
<field name="shipper_faktur_id"/>
<field name="resi_tukar_faktur"/>
<field name="date_terima_tukar_faktur"/>
<field name="due_extension"/>
+ <field name="counter"/>
</field>
+ <notebook position="inside">
+ <page string="Due Extension">
+ <field name="due_line">
+ <tree>
+ <field name="due_id"/>
+ <field name="due_description"/>
+ <field name="due_day_extension"/>
+ <field name="due_approval_status"/>
+ </tree>
+ </field>
+ </page>
+ </notebook>
</field>
</record>
diff --git a/indoteknik_custom/views/bill_receipt.xml b/indoteknik_custom/views/bill_receipt.xml
index cfc268b0..15d82e7b 100644
--- a/indoteknik_custom/views/bill_receipt.xml
+++ b/indoteknik_custom/views/bill_receipt.xml
@@ -6,10 +6,13 @@
<field name="arch" type="xml">
<tree>
<field name="number"/>
- <field name="vendor_id"/>
- <field name="document_date"/>
- <field name="description"/>
- <field name="validated"/>
+ <field name="bill_date"/>
+ <field name="partner_id"/>
+ <field name="is_validated" readonly="1"/>
+ <field name="date_kirim_tukar_faktur"/>
+ <field name="resi_tukar_faktur"/>
+ <field name="date_terima_tukar_faktur"/>
+ <field name="shipper_faktur_id"/>
</tree>
</field>
</record>
@@ -19,16 +22,13 @@
<field name="model">bill.receipt.line</field>
<field name="arch" type="xml">
<tree>
- <field name="partner_ref"/>
- <field name="po_id"/>
- <field name="sale_order_id"/>
- <field name="user_id"/>
- <field name="vendor_id"/>
- <field name="date_approve"/>
- <field name="date_planned"/>
+ <field name="partner_id"/>
+ <field name="invoice_id"/>
+ <field name="date_invoice"/>
<field name="reference"/>
- <field name="amount_untaxed"/>
- <field name="amount_total"/>
+ <field name="total_amt"/>
+ <field name="open_amt"/>
+ <field name="due_date"/>
</tree>
</field>
</record>
@@ -39,29 +39,41 @@
<field name="arch" type="xml">
<form>
<header>
- <button name="validate_button"
+ <button name="generate_bill_line"
+ string="Generate"
+ type="object"
+ class="mr-2 oe_highlight oe_edit_only"
+ />
+ <button name="validate_bill"
string="Validate"
type="object"
+ class="mr-2 oe_highlight oe_edit_only"
/>
- <button name="cancel_button"
- string="Cancel"
- type="object"
+ <button name="copy_date_faktur"
+ string="Copy Date"
+ type="object"
+ class="oe_highlight oe_edit_only"
/>
</header>
<sheet>
<group>
<group>
- <field name="vendor_id" attrs="{'readonly': [('validated', '=', True)]}"/>
- <field name="document_date" attrs="{'readonly': [('validated', '=', True)]}"/>
- <field name="description" attrs="{'readonly': [('validated', '=', True)]}"/>
+ <field name="number"/>
+ <field name="partner_id"/>
+ <field name="bill_date"/>
+ <field name="notification" readonly="1"/>
</group>
<group>
- <field name="validated" readonly="1"/>
+ <field name="is_validated" readonly="1"/>
+ <field name="date_kirim_tukar_faktur"/>
+ <field name="resi_tukar_faktur"/>
+ <field name="date_terima_tukar_faktur"/>
+ <field name="shipper_faktur_id"/>
</group>
</group>
<notebook>
- <page string="Bill Line" name="bill_line">
- <field name="bill_line" attrs="{'readonly': [('validated', '=', True)]}"/>
+ <page string="Bills">
+ <field name="bill_line" attrs="{'readonly': [('is_validated', '=', True)]}"/>
</page>
</notebook>
</sheet>
@@ -69,18 +81,32 @@
</field>
</record>
+ <record id="view_bill_receipt_filter" model="ir.ui.view">
+ <field name="name">bill.receipt.list.select</field>
+ <field name="model">bill.receipt</field>
+ <field name="priority" eval="15"/>
+ <field name="arch" type="xml">
+ <search string="Search Bills">
+ <field name="number"/>
+ <field name="partner_id"/>
+ <field name="bill_line" string="Bill" filter_domain="[('bill_line.invoice_id', 'ilike', self)]"/>
+ </search>
+ </field>
+ </record>
+
<record id="bill_receipt_action" model="ir.actions.act_window">
<field name="name">Bill Line</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">bill.receipt</field>
+ <field name="search_view_id" ref="view_bill_receipt_filter"/>
<field name="view_mode">tree,form</field>
</record>
<menuitem
id="menu_bill_receipt"
- name="Bill Line"
- parent="sale.product_menu_catalog"
- sequence="4"
+ name="Bill Receipt"
+ parent="account.menu_finance_reports"
+ sequence="250"
action="bill_receipt_action"
/>
</odoo>
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 7679f92d..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', '&gt;', '2023-06-28'), ('create_date', '=', False)]}"/>
@@ -72,6 +72,7 @@
<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"/>
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>