summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indoteknik_api/controllers/api_v1/promotion.py106
-rwxr-xr-xindoteknik_custom/__manifest__.py7
-rwxr-xr-xindoteknik_custom/models/__init__.py4
-rw-r--r--indoteknik_custom/models/ir_attachment.py5
-rw-r--r--indoteknik_custom/models/promotion_program.py22
-rw-r--r--indoteknik_custom/models/promotion_program_free_item.py12
-rw-r--r--indoteknik_custom/models/promotion_program_keyword.py8
-rw-r--r--indoteknik_custom/models/promotion_program_line.py70
-rw-r--r--indoteknik_custom/models/typesense_client.py150
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv9
-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.xml85
14 files changed, 632 insertions, 7 deletions
diff --git a/indoteknik_api/controllers/api_v1/promotion.py b/indoteknik_api/controllers/api_v1/promotion.py
index b137fe2e..991a3eb9 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,16 +15,109 @@ 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)])
+ coupon_program = request.env['coupon.program'].search(
+ [('id', '=', id)])
if coupon_program:
data = {
'banner': base_url + 'api/image/coupon.program/x_studio_banner_promo/' + str(coupon_program.id) if coupon_program.x_studio_banner_promo else '',
'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),
+ ])
+ 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))
+ ])
+ pricelist = self.user_pricelist()
+ data = []
+ for line in program_lines:
+ product = request.env['product.product'].v2_api_single_response(line.product_id, pricelist=pricelist)
+ 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': 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)
+ }
+ })
+ price = product['lowest_price']['price']
+ if line.discount_type == 'percentage':
+ product['lowest_price']['discount_percentage'] = line.discount_amount
+ product['lowest_price']['price_discount'] = price - (price * line.discount_amount / 100)
+ if line.discount_type == 'fixed_price':
+ product['lowest_price']['price_discount'] = line.discount_amount
+ product['lowest_price']['discount_percentage'] = round((price - line.discount_amount) / price * 100)
+
+ product.pop('parent', None)
+ product.pop('price', None)
+ product.pop('stock', None)
+ data.append(product)
+ return self.response(data)
+
+
+ @http.route(prefix + 'promotion/product/<id>', auth='public', methods=['GET', 'OPTIONS'])
+ def v1_get_promotion_by_product_id(self, id):
+ id = int(id)
+ current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+ product = request.env['product.template'].search([('id', '=', id)])
+ variant_ids = [variant.id for variant in product.product_variant_ids]
+ program_lines = request.env['promotion.program.line'].search([
+ ('program_id.start_time', '<=', current_time),
+ ('program_id.end_time', '>=', current_time),
+ ('product_id.id', 'in', variant_ids)
+ ])
+
+ data = []
+ ir_attachment = request.env['ir.attachment']
+ for line in program_lines:
+ data.append({
+ 'name': line.name,
+ 'image': ir_attachment.api_image('promotion.program.line', 'image', line.id),
+ 'type': line.promotion_type,
+ 'minimum_purchase_qty': line.minimum_purchase_qty,
+ 'applies_multiply': line.applies_multiply,
+ 'limit_qty': line.limit_qty,
+ 'limit_qty_user': line.limit_qty_user,
+ 'limit_qty_transaction': line.limit_qty_transaction,
+ })
return self.response(data)
- \ No newline at end of file
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py
index eaa5390a..f21fab0c 100755
--- a/indoteknik_custom/__manifest__.py
+++ b/indoteknik_custom/__manifest__.py
@@ -74,8 +74,15 @@
'views/procurement_monitoring_detail.xml',
'views/product_product.xml',
'views/brand_vendor.xml',
+<<<<<<< HEAD
+ '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',
+>>>>>>> 24649f8e939484759ef34e5e68f251d951f63c02
'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 9764e0c0..823463ec 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..bc7f2c49
--- /dev/null
+++ b/indoteknik_custom/models/promotion_program.py
@@ -0,0 +1,22 @@
+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..ddd97765
--- /dev/null
+++ b/indoteknik_custom/models/promotion_program_free_item.py
@@ -0,0 +1,12 @@
+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..b6c9a532
--- /dev/null
+++ b/indoteknik_custom/models/promotion_program_line.py
@@ -0,0 +1,70 @@
+from odoo import fields, models, api
+
+
+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")
+
+ @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)
diff --git a/indoteknik_custom/models/typesense_client.py b/indoteknik_custom/models/typesense_client.py
new file mode 100644
index 00000000..fccd04e3
--- /dev/null
+++ b/indoteknik_custom/models/typesense_client.py
@@ -0,0 +1,150 @@
+from odoo import models
+import typesense
+import logging
+import time
+
+
+_logger = logging.getLogger(__name__)
+_typesense = typesense.Client({
+ 'nodes': [{
+ 'host': 'localhost',
+ 'port': '9090',
+ 'protocol': 'http'
+ }],
+ 'api_key': 'WKWKdwakdjopwakfoij21321fkdmvaskamd'
+})
+
+class Typesense(models.Model):
+ _name = 'typesense.client'
+
+ def _check_collection(self, name):
+ collections = _typesense.collections.retrieve()
+ for collection in collections:
+ if collection['name'] == name:
+ return True
+ return False
+
+ def _init_collection(self, name):
+ is_exist = self._check_collection(name)
+ if is_exist:
+ return False
+
+ schema = {
+ "name": name,
+ "fields": [
+ {"name": ".*", "type": "auto" },
+ {"name": ".*_facet", "type": "auto", "facet": True }
+ ]
+ }
+ _typesense.collections.create(schema)
+ return True
+
+ def _indexing_product(self, limit=500):
+ start_time = time.time()
+ self._init_collection('products')
+
+ query = ["&", "&", ("type", "=", "product"), ("active", "=", True), "|", ("solr_flag", "=", 0), ("solr_flag", "=", 2)]
+ product_templates = self.env['product.template'].search(query, limit=limit)
+
+ counter = 0
+ documents = []
+ for product_template in product_templates:
+ counter += 1
+ template_time = time.time()
+ document = self._map_product_document(product_template)
+ documents.append(document)
+ product_template.solr_flag = 1
+ _logger.info('[SYNC_PRODUCT_TO_TYPESENSE] {}/{} {:.6f}'.format(counter, limit, time.time() - template_time))
+ _logger.info('[SYNC_PRODUCT_TO_TYPESENSE] Success add to typesense product %s' % product_template.id)
+
+ _typesense.collections['products'].documents.import_(documents, {'action': 'upsert'})
+ end_time = time.time()
+ _logger.info("[SYNC_PRODUCT_TO_SOLR] Finish task add to solr. Time taken: {:.6f} seconds".format(end_time - start_time))
+
+ def _map_product_document(self, product_template):
+ price_excl_after_disc = price_excl = discount = tax = 0
+ variants_name = variants_code = ''
+ flashsale_data = tier1 = tier2 = tier3 = {}
+ if product_template.product_variant_count > 1:
+ for variant in product_template.product_variant_ids:
+ if price_excl_after_disc == 0 or variant._get_website_price_after_disc_and_tax() < price_excl_after_disc:
+ price_excl = variant._get_website_price_exclude_tax()
+ price_excl_after_disc = variant._get_website_price_after_disc_and_tax()
+ discount = variant._get_website_disc(0)
+ tax = variant._get_website_tax()
+ flashsale_data = variant._get_flashsale_price()
+ # add price tiering for base price, discount, and price after discount (tier 1 - 3)
+ tier1 = variant._get_pricelist_tier1()
+ tier2 = variant._get_pricelist_tier2()
+ tier3 = variant._get_pricelist_tier3()
+ else:
+ price_excl_after_disc = price_excl_after_disc
+ price_excl = price_excl
+ discount = discount
+ tax = tax
+ flashsale_data = flashsale_data
+ tier1 = tier1
+ tier2 = tier2
+ tier3 = tier3
+ variants_name += variant.display_name or ''+', '
+ variants_code += variant.default_code or ''+', '
+ else:
+ variants_name = product_template.display_name
+ price_excl = product_template.product_variant_id._get_website_price_exclude_tax()
+ discount = product_template.product_variant_id._get_website_disc(0)
+ price_excl_after_disc = product_template.product_variant_id._get_website_price_after_disc_and_tax()
+ tax = product_template.product_variant_id._get_website_tax()
+ flashsale_data = product_template.product_variant_id._get_flashsale_price()
+ tier1 = product_template.product_variant_id._get_pricelist_tier1()
+ tier2 = product_template.product_variant_id._get_pricelist_tier2()
+ tier3 = product_template.product_variant_id._get_pricelist_tier3()
+
+ category_id = ''
+ category_name = ''
+ for category in product_template.public_categ_ids:
+ category_id = category.id
+ category_name = category.name
+
+ document = {
+ 'id': str(product_template.id),
+ 'display_name': product_template.display_name,
+ 'name': product_template.name,
+ 'default_code': product_template.default_code or '',
+ 'product_rating': product_template.virtual_rating,
+ 'product_id': product_template.id,
+ 'image': self.env['ir.attachment'].api_image('product.template', 'image_512', product_template.id),
+ 'price': price_excl,
+ 'discount': discount,
+ 'price_discount': price_excl_after_disc,
+ 'tax': tax,
+ 'variant_total': product_template.product_variant_count,
+ 'stock_total': product_template.qty_stock_vendor,
+ 'weight': product_template.weight,
+ 'manufacture_id': product_template.x_manufacture.id or 0,
+ 'manufacture_name': product_template.x_manufacture.x_name or '',
+ 'manufacture_name': product_template.x_manufacture.x_name or '',
+ 'image_promotion_1': self.env['ir.attachment'].api_image('x_manufactures', 'image_promotion_1', product_template.x_manufacture.id),
+ 'image_promotion_2': self.env['ir.attachment'].api_image('x_manufactures', 'image_promotion_2', product_template.x_manufacture.id),
+ 'category_id': category_id or 0,
+ 'category_name': category_name or '',
+ 'category_name': category_name or '',
+ 'variants_name_t': variants_name,
+ 'variants_code_t': variants_code,
+ 'search_rank': product_template.search_rank,
+ 'search_rank_weekly': product_template.search_rank_weekly,
+ 'flashsale_id': flashsale_data['flashsale_id'] or 0,
+ 'flashsale_name': flashsale_data['flashsale_name'] or '',
+ 'flashsale_base_price': flashsale_data['flashsale_base_price'] or 0,
+ 'flashsale_discount': flashsale_data['flashsale_discount'] or 0,
+ 'flashsale_price': flashsale_data['flashsale_price'] or 0,
+ 'discount_tier1': tier1['discount_tier1'] or 0,
+ 'price_tier1': tier1['price_tier1'] or 0,
+ 'discount_tier2': tier2['discount_tier2'] or 0,
+ 'price_tier2': tier2['price_tier2'] or 0,
+ 'discount_tier3': tier3['discount_tier3'] or 0,
+ 'price_tier3': tier3['price_tier3'] or 0
+ }
+
+ return document
+
+
diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv
index 26e852f1..3d23529f 100755
--- a/indoteknik_custom/security/ir.model.access.csv
+++ b/indoteknik_custom/security/ir.model.access.csv
@@ -49,6 +49,13 @@ 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
+<<<<<<< HEAD
+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 \ No newline at end of file
+access_requisition_purchase_match,access.requisition.purchase.match,model_requisition_purchase_match,,1,1,1,1
+>>>>>>> 24649f8e939484759ef34e5e68f251d951f63c02
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..e15bb0d7
--- /dev/null
+++ b/indoteknik_custom/views/promotion_program_line.xml
@@ -0,0 +1,85 @@
+<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', '=', 'specific_product')]}"
+ >
+ <field name="line_free_item" />
+ </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