summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indoteknik_api/controllers/__init__.py3
-rw-r--r--indoteknik_api/controllers/api_v1/__init__.py5
-rw-r--r--indoteknik_api/controllers/api_v1/category.py37
-rw-r--r--indoteknik_api/controllers/api_v1/flash_sale.py57
-rw-r--r--indoteknik_api/controllers/api_v1/manufacture.py37
-rw-r--r--indoteknik_api/controllers/api_v1/product.py83
-rw-r--r--indoteknik_api/controllers/api_v1/sale_order.py (renamed from indoteknik_api/controllers/sale_order_controller.py)10
-rw-r--r--indoteknik_api/controllers/controller.py33
-rw-r--r--indoteknik_api/controllers/product_controller.py151
-rw-r--r--indoteknik_api/models/product_pricelist.py22
-rw-r--r--indoteknik_api/models/product_template.py63
-rwxr-xr-xindoteknik_custom/models/product_template.py7
-rw-r--r--indoteknik_custom/models/sale_advance_payment_inv.py167
-rwxr-xr-xindoteknik_custom/models/sale_monitoring.py2
-rwxr-xr-xindoteknik_custom/models/sale_monitoring_detail.py2
-rwxr-xr-xindoteknik_custom/models/x_manufactures.py2
-rw-r--r--indoteknik_custom/report/report_deliveryslip2.xml214
-rw-r--r--indoteknik_custom/static/src/js/delivery_order.js44
-rwxr-xr-xindoteknik_custom/views/x_manufactures.xml2
19 files changed, 755 insertions, 186 deletions
diff --git a/indoteknik_api/controllers/__init__.py b/indoteknik_api/controllers/__init__.py
index 4c317fdc..30664b92 100644
--- a/indoteknik_api/controllers/__init__.py
+++ b/indoteknik_api/controllers/__init__.py
@@ -1,3 +1,2 @@
from . import controller
-from . import product_controller
-from . import sale_order_controller
+from . import api_v1 \ No newline at end of file
diff --git a/indoteknik_api/controllers/api_v1/__init__.py b/indoteknik_api/controllers/api_v1/__init__.py
new file mode 100644
index 00000000..b569a012
--- /dev/null
+++ b/indoteknik_api/controllers/api_v1/__init__.py
@@ -0,0 +1,5 @@
+from . import category
+from . import flash_sale
+from . import manufacture
+from . import product
+from . import sale_order \ No newline at end of file
diff --git a/indoteknik_api/controllers/api_v1/category.py b/indoteknik_api/controllers/api_v1/category.py
new file mode 100644
index 00000000..585e3eda
--- /dev/null
+++ b/indoteknik_api/controllers/api_v1/category.py
@@ -0,0 +1,37 @@
+from .. import controller
+from odoo import http
+from odoo.http import request
+
+
+class Category(controller.Controller):
+ prefix = '/api/v1/'
+
+ @http.route(prefix + 'category/page/<page>', auth='public', methods=['GET'])
+ def get_category(self, **kw):
+ if not self.authenticate():
+ return self.response(code=401, description='Unauthorized')
+
+ category_ids = []
+ page = kw.get('page')
+ if page == 'flash-sale':
+ active_flash_sale = request.env['product.pricelist'].get_active_flash_sale()
+ if active_flash_sale:
+ category_ids = [x.id for item in active_flash_sale.item_ids for x in item.product_id.public_categ_ids]
+ elif page == 'manufacture':
+ manufacture_id = kw.get('manufacture_id')
+ if not manufacture_id:
+ return self.response(code=400, description='manufacture_id is required')
+ product_variants = request.env['product.product'].search([('x_manufacture', '=', int(manufacture_id))])
+ category_ids = [x.id for variant in product_variants for x in variant.public_categ_ids]
+ else:
+ return self.response(code=400, description='page possible value is flash-sale, manufacture')
+
+ categories = request.env['product.public.category'].search([('id', 'in', category_ids)])
+ data = []
+ for category in categories:
+ data.append({
+ 'id': category.id,
+ 'name': category.name
+ })
+ return self.response(data)
+ \ No newline at end of file
diff --git a/indoteknik_api/controllers/api_v1/flash_sale.py b/indoteknik_api/controllers/api_v1/flash_sale.py
new file mode 100644
index 00000000..7f0166ee
--- /dev/null
+++ b/indoteknik_api/controllers/api_v1/flash_sale.py
@@ -0,0 +1,57 @@
+from datetime import datetime
+import logging
+from .. import controller
+from odoo import http
+from odoo.http import request
+
+_logger = logging.getLogger(__name__)
+
+
+class FlashSale(controller.Controller):
+ prefix = '/api/v1/'
+
+ @http.route(prefix + 'flash_sale', auth='public', methods=['GET'])
+ def get_flash_sale(self, **kw):
+ try:
+ if not self.authenticate():
+ return self.response(code=401, description='Unauthorized')
+ base_url = request.env['ir.config_parameter'].get_param('web.base.url')
+ active_flash_sale = request.env['product.pricelist'].get_active_flash_sale()
+ data = {}
+ if active_flash_sale:
+ flash_sale_product_variant_ids = [x.product_id.id for x in active_flash_sale.item_ids]
+ query = [('id', 'in', flash_sale_product_variant_ids)]
+
+ product_name = kw.get('product_name')
+ if product_name:
+ product_name = '%' + product_name.replace(' ', '%') + '%'
+ query += ['|', ('name', 'ilike', product_name), ('default_code', 'ilike', product_name)]
+
+ manufactures = kw.get('manufactures')
+ if manufactures:
+ query += [('x_manufacture', 'in', [int(x) for x in manufactures.split(',')])]
+
+ categories = kw.get('categories')
+ if categories:
+ query += [('public_categ_ids', 'child_of', [int(x) for x in categories.split(',')])]
+
+ product_variants = request.env['product.product'].search(query)
+ product_variant_ids = [x.id for x in product_variants]
+
+ query = [('product_variant_ids', 'in', product_variant_ids)]
+ product_templates = self.search_filter('product.template', kw, query)
+ data = {
+ 'flash_sale': {
+ 'banner': base_url + 'api/image/product.pricelist/banner/' + str(active_flash_sale.id) if active_flash_sale.banner else '',
+ 'duration': round((active_flash_sale.end_date - datetime.now()).total_seconds()),
+ 'product_total': request.env['product.template'].search_count(query),
+ 'products': [request.env['product.template'].api_single_response(x) for x in product_templates]
+ }
+ }
+ return self.response(data)
+ else:
+ return self.response(code=404, description='Data not found')
+ except Exception as e:
+ _logger.info(self.prefix_url + '/flash_sale: ' + str(e))
+ return self.response(code=500, description='Internal server error')
+ \ No newline at end of file
diff --git a/indoteknik_api/controllers/api_v1/manufacture.py b/indoteknik_api/controllers/api_v1/manufacture.py
new file mode 100644
index 00000000..ba2e3be9
--- /dev/null
+++ b/indoteknik_api/controllers/api_v1/manufacture.py
@@ -0,0 +1,37 @@
+from .. import controller
+from odoo import http
+from odoo.http import request
+
+
+class Manufacture(controller.Controller):
+ prefix = '/api/v1/'
+
+ @http.route(prefix + 'manufacture/page/<page>', auth='public', methods=['GET'])
+ def get_manufacture(self, **kw):
+ if not self.authenticate():
+ return self.response(code=401, description='Unauthorized')
+
+ manufacture_ids = []
+ page = kw.get('page')
+ if page == 'flash-sale':
+ active_flash_sale = request.env['product.pricelist'].get_active_flash_sale()
+ if active_flash_sale:
+ manufacture_ids = [x.product_id.x_manufacture.id for x in active_flash_sale.item_ids]
+ elif page == 'category':
+ category_id = kw.get('category_id')
+ if not category_id:
+ return self.response(code=400, description='category_id is required')
+ product_variants = request.env['product.product'].search([('public_categ_ids', '=', int(category_id))])
+ manufacture_ids = [x.x_manufacture.id for x in product_variants]
+ else:
+ return self.response(code=400, description='page possible value is flash-sale, category')
+
+ manufactures = request.env['x_manufactures'].search([('id', 'in', manufacture_ids)])
+ data = []
+ for manufacture in manufactures:
+ data.append({
+ 'id': manufacture.id,
+ 'name': manufacture.x_name
+ })
+ return self.response(data)
+ \ No newline at end of file
diff --git a/indoteknik_api/controllers/api_v1/product.py b/indoteknik_api/controllers/api_v1/product.py
new file mode 100644
index 00000000..493677fd
--- /dev/null
+++ b/indoteknik_api/controllers/api_v1/product.py
@@ -0,0 +1,83 @@
+from .. import controller
+from odoo import http
+from odoo.http import request
+
+
+class Product(controller.Controller):
+ prefix = '/api/v1/'
+
+ @http.route(prefix + 'product', auth='public', methods=['GET'])
+ def get_product(self, **kw):
+ if not self.authenticate():
+ return self.response(code=401, description='Unauthorized')
+
+ name = kw.get('name')
+ manufactures = kw.get('manufactures')
+ categories = kw.get('categories')
+
+ require_betweens = ['name', 'manufactures', 'categories']
+ is_fulfill = False
+ for required in require_betweens:
+ if kw.get(required):
+ is_fulfill = True
+
+ # If not fulfill in require_between
+ if not is_fulfill:
+ return self.response(code=400, description='name or manufactures or categories is required')
+
+ query = [('sale_ok', '=', True)]
+
+ if name:
+ name = '%' + name.replace(' ', '%') + '%'
+ query += [
+ '|',
+ ('default_code', 'ilike', name),
+ ('name', 'ilike', name),
+ ]
+
+ if manufactures:
+ query.append(('x_manufacture', 'in', [int(x) for x in manufactures.split(',')]))
+
+ if categories:
+ query.append(('public_categ_ids', 'child_of', [int(x) for x in categories.split(',')]))
+
+ product_variants = request.env['product.product'].search(query)
+ product_variant_ids = [x.id for x in product_variants]
+
+ query = [('product_variant_ids', 'in', product_variant_ids)]
+ limit = int(kw.get('limit', 0))
+ offset = int(kw.get('offset', 0))
+ order = kw.get('order')
+
+ orders = ['product_rating desc']
+ if order != 'price-asc':
+ orders.append('web_price_sorting desc')
+ if order == 'price-asc':
+ orders.append('web_price_sorting asc')
+ elif order == 'latest':
+ orders.append('create_date desc')
+ orders = ','.join(orders)
+ product_templates = request.env['product.template'].search(query, limit=limit, offset=offset, order=orders)
+ data = {
+ 'product_total': request.env['product.template'].search_count(query),
+ 'products': [request.env['product.template'].api_single_response(x) for x in product_templates]
+ }
+ return self.response(data)
+
+ @http.route(prefix + 'product/<id>', auth='public', methods=['GET'])
+ def get_product_by_id(self, **kw):
+ if not self.authenticate():
+ return self.response(code=401, description='Unauthorized')
+
+ id = kw.get('id')
+ if not id:
+ return self.response(code=400, description='id is required')
+
+ 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'].api_single_response(x, with_detail=True) for x in product_templates]
+
+ return self.response(data)
+ \ No newline at end of file
diff --git a/indoteknik_api/controllers/sale_order_controller.py b/indoteknik_api/controllers/api_v1/sale_order.py
index 741d4bf8..99302a66 100644
--- a/indoteknik_api/controllers/sale_order_controller.py
+++ b/indoteknik_api/controllers/api_v1/sale_order.py
@@ -1,12 +1,12 @@
-from . import controller
+from .. import controller
from odoo import http
from odoo.http import request
-class SaleOrderController(controller.Controller):
+class SaleOrder(controller.Controller):
@http.route('/api/sale_order/invoiced', auth='public', methods=['GET'])
def get_sale_order_invoiced_by_partner_id(self, **kw):
- self.authenticate(kw)
+ self.authenticate()
partner_id = kw.get('partner_id')
if not partner_id:
return self.response(code=400, description='Field partner_id is required')
@@ -25,7 +25,7 @@ class SaleOrderController(controller.Controller):
('invoice_status', '=', 'invoiced'),
('invoice_status', '=', 'to_invoice')
]
- sale_orders = self.search_with_api_params('sale.order', kw, default_domain)
+ sale_orders = self.search_filter('sale.order', kw, default_domain)
for sale_order in sale_orders:
pickings = []
for picking in sale_order.picking_ids:
@@ -71,7 +71,7 @@ class SaleOrderController(controller.Controller):
partner_child_ids = [v['id'] for v in partner_childs] + [int(partner_id)]
default_domain.append(('partner_id', 'in', partner_child_ids))
- sale_order = self.search_with_api_params('sale.order', kw, default_domain)
+ sale_order = self.search_filter('sale.order', kw, default_domain)
orders = []
for order in sale_order.order_line:
orders.append({
diff --git a/indoteknik_api/controllers/controller.py b/indoteknik_api/controllers/controller.py
index fb5f1fce..c02f6b60 100644
--- a/indoteknik_api/controllers/controller.py
+++ b/indoteknik_api/controllers/controller.py
@@ -1,3 +1,4 @@
+from array import array
import datetime
import base64
import json
@@ -8,11 +9,29 @@ from pytz import timezone
class Controller(http.Controller):
- def authenticate(self, kw):
- db = kw.get('db')
- username = kw.get('username')
- password = kw.get('password')
- request.session.authenticate(db, username, password)
+ def authenticate(self):
+ try:
+ wsgienv = request.httprequest.environ
+ db = wsgienv['HTTP_DB']
+ username = wsgienv['HTTP_USERNAME']
+ password = wsgienv['HTTP_PASSWORD']
+ request.session.authenticate(db, username, password)
+ return True
+ except:
+ return False
+
+ def validate_request(self, rules: dict, kw: dict):
+ validation = {
+ 'status': True,
+ 'reason': []
+ }
+ for key in rules:
+ values = rules[key]
+ for value in values:
+ if value == 'required' and not kw.get(key):
+ validation['status'] = False
+ validation['reason'].append(key + ' is ' + value)
+ return validation
def time_to_str(self, object, format):
time = ''
@@ -33,13 +52,13 @@ class Controller(http.Controller):
response = json.dumps(response)
return request.make_response(response, [('Content-Type', 'application/json')])
- def search_with_api_params(self, model: str, kw, domain=[]):
+ def search_filter(self, model: str, kw: dict, query: array = []):
""" To search data by default API Params if exist """
limit = kw.get('limit', 0)
offset = kw.get('offset', 0)
order = kw.get('order', '')
- return request.env[model].search(domain, limit=int(limit), offset=int(offset), order=order)
+ return request.env[model].search(query, limit=int(limit), offset=int(offset), order=order)
@http.route('/api/image/<model>/<field>/<id>', auth='public', methods=['GET'])
def get_image(self, model, field, id):
diff --git a/indoteknik_api/controllers/product_controller.py b/indoteknik_api/controllers/product_controller.py
deleted file mode 100644
index 6101952f..00000000
--- a/indoteknik_api/controllers/product_controller.py
+++ /dev/null
@@ -1,151 +0,0 @@
-from datetime import datetime
-import logging
-from . import controller
-from odoo import http
-from odoo.http import request
-
-_logger = logging.getLogger(__name__)
-
-
-class ProductController(controller.Controller):
- prefix_url = '/api/product'
-
- # TODO: separate function for get manufacture and promotion by product_id
- @http.route(prefix_url + '/search', auth='public', methods=['GET'])
- def search_product(self, **kw):
- self.authenticate(kw)
- base_url = request.env['ir.config_parameter'].sudo().get_param('web.base.url')
-
- query = kw.get('query')
- if not query:
- return self.response(code=400, description='Field query is required')
-
- query = '%' + query.replace(' ', '%') + '%'
- domain = [
- ('sale_ok', '=', True),
- '|',
- ('default_code', 'ilike', query),
- ('name', 'ilike', query)
- ]
-
- manufactures = kw.get('manufactures')
- if manufactures:
- manufactures = [int(x) for x in manufactures.split(',')]
- domain.append(('x_manufacture', 'in', manufactures))
-
- categories = kw.get('categories')
- if categories:
- categories = [int(x) for x in categories.split(',')]
- domain.append(('public_categ_ids', 'child_of', categories))
-
- product_variants = request.env['product.product'].search(domain)
- product_variant_ids = [x.id for x in product_variants]
-
- domain = [('product_variant_ids', 'in', product_variant_ids)]
- product_templates = self.search_with_api_params('product.template', kw, domain)
- data = {
- 'total_records': len(request.env['product.template'].search(domain)),
- 'products': []
- }
- for product_template in product_templates:
- discount_price = 0
- price = product_template.web_price
- if price > 0:
- if product_template.web_tax_id:
- if not product_template.web_tax_id.price_include:
- price += (price * product_template.web_tax_id.amount / 100)
- else:
- price += (price * 11 / 100)
-
- promotion = self.get_promotion_by_product(product_template)
- if len(promotion) > 0:
- discount_price = price - (price * promotion['discount_percentage'] / 100)
-
- manufacture = self.get_manufacture_by_product(product_template)
- stock = self.get_stock_by_product(product_template)
-
- # TODO: remove price and discount_price if old indoteknik not use
- data['products'].append({
- 'id': product_template.id,
- 'image': base_url + 'api/image/product.template/image_128/' + str(product_template.id) if product_template.image_128 else '',
- 'name': product_template.name,
- # 'lowest_price': request.env['product.pricelist'].get_lowest_product_variant_price(product_template.id, 4),
- 'price': price,
- 'discount_price': discount_price,
- 'total_variant': len(product_template.product_variant_ids),
- 'stock': stock,
- 'manufacture': manufacture,
- 'promotion': promotion,
- })
- return self.response(data)
-
- @http.route(prefix_url + '/flash_sale', auth='public', methods=['GET'])
- def get_flash_sale_product(self, **kw):
- try:
- self.authenticate(kw)
- base_url = request.env['ir.config_parameter'].sudo().get_param('web.base.url')
- product_pricelist_default = request.env['ir.config_parameter'].sudo().get_param('product.pricelist.default')
- active_flash_sale = request.env['product.pricelist'].get_active_flash_sale()
- data = {}
- if active_flash_sale:
- flash_sale = {
- 'banner': base_url + 'api/image/product.pricelist/banner/' + str(active_flash_sale.id) if active_flash_sale.banner else '',
- 'duration': round((active_flash_sale.end_date - datetime.now()).total_seconds()),
- 'products': []
- }
- product_pricelist_item = request.env['product.pricelist.item'].search([('pricelist_id', '=', active_flash_sale.id)])
- product_variant_ids = [x.product_id.id for x in product_pricelist_item]
- product_templates = self.search_with_api_params('product.template', kw, [('product_variant_ids', 'in', product_variant_ids)])
- for product in product_templates:
- flash_sale['products'].append({
- 'id': product.id,
- 'name': product.name,
- 'image': base_url + 'api/image/product.template/image_128/' + str(product.id) if product.image_128 else '',
- 'lowest_price': request.env['product.pricelist'].get_lowest_product_variant_price(product.id, int(product_pricelist_default)),
- 'stock': self.get_stock_by_product(product),
- 'total_variant': len(product.product_variant_ids),
- 'manufacture': self.get_manufacture_by_product(product),
- 'promotion': self.get_promotion_by_product(product),
- })
- data.update({'flash_sale': flash_sale})
- else:
- return self.response(code=404, description='Data not found')
- except Exception as e:
- _logger.info(self.prefix_url + '/flash_sale: ' + str(e))
- return self.response(code=500, description='Internal server error')
- return self.response(data)
-
- def get_stock_by_product(self, product_template: object):
- stock = 0
- for product_variant in product_template.product_variant_ids:
- stock += product_variant.qty_stock_vendor
- return stock
-
- def get_manufacture_by_product(self, product_template: object):
- manufacture = {}
- if product_template.x_manufacture:
- manufacture.update({
- 'id': product_template.x_manufacture.id,
- 'name': product_template.x_manufacture.x_name,
- })
- return manufacture
-
- def get_promotion_by_product(self, product_template: object):
- base_url = request.env['ir.config_parameter'].sudo().get_param('web.base.url')
- promotion = {}
- if product_template.x_manufacture:
- domain = [
- ('rule_products_domain', 'ilike', product_template.x_manufacture.x_name),
- ('active', '=', True)
- ]
- coupon_program = request.env['coupon.program'].search(domain, limit=1)
- if coupon_program:
- icon_1 = (base_url + 'api/image/coupon.program/x_studio_field_Ifopn/' + str(coupon_program.id)) if coupon_program.x_studio_field_Ifopn else ''
- icon_2 = (base_url + 'api/image/coupon.program/x_studio_field_2Ul77/' + str(coupon_program.id)) if coupon_program.x_studio_field_2Ul77 else ''
- promotion.update({
- 'name': coupon_program.name,
- 'discount_percentage': coupon_program.discount_percentage,
- 'icon_1': icon_1,
- 'icon_2': icon_2
- })
- return promotion \ No newline at end of file
diff --git a/indoteknik_api/models/product_pricelist.py b/indoteknik_api/models/product_pricelist.py
index f40efd5d..4e7f6adf 100644
--- a/indoteknik_api/models/product_pricelist.py
+++ b/indoteknik_api/models/product_pricelist.py
@@ -41,10 +41,11 @@ class ProductPricelist(models.Model):
if price > 0:
product = self.env['product.product'].browse(product_id)
if product:
- for tax in product.taxes_id:
- if not tax.price_include:
- price *= (100 + tax.amount) / 100
-
+ product = product.product_tmpl_id
+ if product.web_tax_id and not product.web_tax_id.price_include:
+ price *= (100 + product.web_tax_id.amount) / 100
+
+ price_discount = price
for discount in discounts:
price_discount *= (100 - discount) / 100
@@ -57,13 +58,12 @@ class ProductPricelist(models.Model):
'discount_percentage': discount_percentage
}
- def get_lowest_product_variant_price(self, product_id, pricelist_id):
+ def get_lowest_product_variant_price(self, product_template: dict, pricelist_id: int):
"""
- @param product_id: id of product.template
+ @param product_template: dict of product_template
@param pricelist_id: id of pricelist which have default price
@return price: object
"""
- product_template = self.env['product.template'].browse(product_id)
product_variant_ids = [x.id for x in product_template.product_variant_ids]
product = self.env['product.pricelist.item'].search([
('pricelist_id', '=', pricelist_id),
@@ -99,11 +99,3 @@ class ProductPricelist(models.Model):
if product_id in pricelist_product_ids:
return pricelist.id
return False
-
- # TODO: need testing
- def get_active_flash_sale_items(self):
- pricelist = self.get_active_flash_sale()
- pricelist_item = False
- if pricelist:
- pricelist_item = [x.product_id for x in pricelist.item_ids]
- return pricelist_item
diff --git a/indoteknik_api/models/product_template.py b/indoteknik_api/models/product_template.py
index e69de29b..aa35d922 100644
--- a/indoteknik_api/models/product_template.py
+++ b/indoteknik_api/models/product_template.py
@@ -0,0 +1,63 @@
+from odoo import models, fields
+
+
+class ProductTemplate(models.Model):
+ _inherit = 'product.template'
+
+ def api_single_response(self, product_template, with_detail=False):
+ base_url = self.env['ir.config_parameter'].get_param('web.base.url')
+ product_pricelist_default = self.env['ir.config_parameter'].get_param('product.pricelist.default')
+ product_pricelist_default = int(product_pricelist_default)
+ data = {
+ 'id': product_template.id,
+ 'image': base_url + 'api/image/product.template/image_128/' + str(product_template.id) if product_template.image_128 else '',
+ 'code': product_template.default_code or '',
+ 'name': product_template.name,
+ 'lowest_price': self.env['product.pricelist'].get_lowest_product_variant_price(product_template, product_pricelist_default),
+ 'variant_total': len(product_template.product_variant_ids),
+ 'stock_total': product_template.qty_stock_vendor,
+ 'weight': product_template.weight,
+ 'manufacture': self.api_manufacture(product_template),
+ 'categories': self.api_categories(product_template),
+ }
+ if with_detail:
+ detail_data = {
+ 'image': base_url + 'api/image/product.template/image_512/' + str(product_template.id) if product_template.image_512 else '',
+ 'variants': [],
+ 'description': product_template.website_description or '',
+ }
+ for variant in product_template.product_variant_ids:
+ detail_data['variants'].append({
+ 'id': variant.id,
+ 'code': variant.default_code or '',
+ 'name': variant.display_name,
+ 'price': self.env['product.pricelist'].compute_price(product_pricelist_default, variant.id),
+ 'stock': variant.qty_stock_vendor,
+ 'weight': variant.weight,
+ })
+ data.update(detail_data)
+ return data
+
+ def api_manufacture(self, product_template):
+ base_url = self.env['ir.config_parameter'].get_param('web.base.url')
+ if product_template.x_manufacture:
+ manufacture = product_template.x_manufacture
+ return {
+ 'id': manufacture.id,
+ 'name': manufacture.x_name,
+ 'image_promotion_1': base_url + 'api/image/x_manufactures/image_promotion_1/' + str(manufacture.id) if manufacture.image_promotion_1 else '',
+ 'image_promotion_2': base_url + 'api/image/x_manufactures/image_promotion_2/' + str(manufacture.id) if manufacture.image_promotion_2 else '',
+ }
+ return {}
+
+ def api_categories(self, product_template):
+ if product_template.public_categ_ids:
+ categories = []
+ for category in product_template.public_categ_ids:
+ categories.append({
+ 'id': category.id,
+ 'name': category.name
+ })
+ return categories
+ return []
+ \ No newline at end of file
diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py
index b1dcabb6..8291a517 100755
--- a/indoteknik_custom/models/product_template.py
+++ b/indoteknik_custom/models/product_template.py
@@ -30,15 +30,14 @@ class ProductTemplate(models.Model):
help="Punya promotion program gak?")
product_rating = fields.Float('Product Rating', help="Digunakan untuk sorting product di website")
last_calculate_rating = fields.Datetime("Last Calculate Rating")
- web_price_sorting = fields.Float('Web Price Sorting',
- help='Hanya digunakan untuk sorting di web, harga tidak berlaku')
+ web_price_sorting = fields.Float('Web Price Sorting', help='Hanya digunakan untuk sorting di web, harga tidak berlaku')
def _compute_qty_stock_vendor(self):
for product_template in self:
product_template.qty_stock_vendor = 0
for product_variant in product_template.product_variant_ids:
- product_template.qty_stock_vendor += product_variant.qty_stock_vendor
-
+ product_template.qty_stock_vendor += int(product_variant.qty_stock_vendor)
+
def _compute_web_price(self):
for template in self:
product = self.env['product.product'].search([('product_tmpl_id', '=', template.id)], limit=1)
diff --git a/indoteknik_custom/models/sale_advance_payment_inv.py b/indoteknik_custom/models/sale_advance_payment_inv.py
new file mode 100644
index 00000000..e07d0ee0
--- /dev/null
+++ b/indoteknik_custom/models/sale_advance_payment_inv.py
@@ -0,0 +1,167 @@
+from odoo import models, fields
+from odoo.exceptions import UserError
+
+
+class SaleAdvancePaymentInv(models.TransientModel):
+ _inherit = 'sale.advance.payment.inv'
+
+ def create_invoices(self):
+ sale_orders = self.env['sale.order'].browse(self._context.get('active_ids', []))
+
+ if self.advance_payment_method == 'delivered':
+ sale_orders._create_invoices(final=self.deduct_down_payments)
+ else:
+ # Create deposit product if necessary
+ if not self.product_id:
+ vals = self._prepare_deposit_product()
+ self.product_id = self.env['product.product'].create(vals)
+ self.env['ir.config_parameter'].sudo().set_param('sale.default_deposit_product_id', self.product_id.id)
+
+ sale_line_obj = self.env['sale.order.line']
+ for order in sale_orders:
+ amount, name = self._get_advance_details(order)
+
+ if self.product_id.invoice_policy != 'order':
+ raise UserError(
+ _('The product used to invoice a down payment should have an invoice policy set to "Ordered quantities". Please update your deposit product to be able to create a deposit invoice.'))
+ if self.product_id.type != 'service':
+ raise UserError(
+ _("The product used to invoice a down payment should be of type 'Service'. Please use another product or update this product."))
+ taxes = self.product_id.taxes_id.filtered(
+ lambda r: not order.company_id or r.company_id == order.company_id)
+ tax_ids = order.fiscal_position_id.map_tax(taxes, self.product_id, order.partner_shipping_id).ids
+ analytic_tag_ids = []
+ for line in order.order_line:
+ analytic_tag_ids = [(4, analytic_tag.id, None) for analytic_tag in line.analytic_tag_ids]
+
+ so_line_values = self._prepare_so_line(order, analytic_tag_ids, tax_ids, amount)
+ so_line = sale_line_obj.create(so_line_values)
+ self._create_invoice(order, so_line, amount)
+ if self._context.get('open_invoices', False):
+ return sale_orders.action_view_invoice()
+ return {'type': 'ir.actions.act_window_close'}
+
+ # def _create_invoices(self, grouped=False, final=False, date=None):
+ # """
+ # Create the invoice associated to the SO.
+ # :param grouped: if True, invoices are grouped by SO id. If False, invoices are grouped by
+ # (partner_invoice_id, currency)
+ # :param final: if True, refunds will be generated if necessary
+ # :returns: list of created invoices
+ # """
+ # if not self.env['account.move'].check_access_rights('create', False):
+ # try:
+ # self.check_access_rights('write')
+ # self.check_access_rule('write')
+ # except AccessError:
+ # return self.env['account.move']
+ #
+ # # 1) Create invoices.
+ # invoice_vals_list = []
+ # invoice_item_sequence = 0 # Incremental sequencing to keep the lines order on the invoice.
+ # for order in self:
+ # order = order.with_company(order.company_id)
+ # current_section_vals = None
+ # down_payments = order.env['sale.order.line']
+ #
+ # invoice_vals = order._prepare_invoice()
+ # invoiceable_lines = order._get_invoiceable_lines(final)
+ #
+ # if not any(not line.display_type for line in invoiceable_lines):
+ # continue
+ #
+ # invoice_line_vals = []
+ # down_payment_section_added = False
+ # for line in invoiceable_lines:
+ # if not down_payment_section_added and line.is_downpayment:
+ # # Create a dedicated section for the down payments
+ # # (put at the end of the invoiceable_lines)
+ # invoice_line_vals.append(
+ # (0, 0, order._prepare_down_payment_section_line(
+ # sequence=invoice_item_sequence,
+ # )),
+ # )
+ # down_payment_section_added = True
+ # invoice_item_sequence += 1
+ # invoice_line_vals.append(
+ # (0, 0, line._prepare_invoice_line(
+ # sequence=invoice_item_sequence,
+ # )),
+ # )
+ # invoice_item_sequence += 1
+ #
+ # invoice_vals['invoice_line_ids'] += invoice_line_vals
+ # invoice_vals_list.append(invoice_vals)
+ #
+ # if not invoice_vals_list:
+ # raise self._nothing_to_invoice_error()
+ #
+ # # 2) Manage 'grouped' parameter: group by (partner_id, currency_id).
+ # if not grouped:
+ # new_invoice_vals_list = []
+ # invoice_grouping_keys = self._get_invoice_grouping_keys()
+ # invoice_vals_list = sorted(invoice_vals_list, key=lambda x: [x.get(grouping_key) for grouping_key in invoice_grouping_keys])
+ # for grouping_keys, invoices in groupby(invoice_vals_list, key=lambda x: [x.get(grouping_key) for grouping_key in invoice_grouping_keys]):
+ # origins = set()
+ # payment_refs = set()
+ # refs = set()
+ # ref_invoice_vals = None
+ # for invoice_vals in invoices:
+ # if not ref_invoice_vals:
+ # ref_invoice_vals = invoice_vals
+ # else:
+ # ref_invoice_vals['invoice_line_ids'] += invoice_vals['invoice_line_ids']
+ # origins.add(invoice_vals['invoice_origin'])
+ # payment_refs.add(invoice_vals['payment_reference'])
+ # refs.add(invoice_vals['ref'])
+ # ref_invoice_vals.update({
+ # 'ref': ', '.join(refs)[:2000],
+ # 'invoice_origin': ', '.join(origins),
+ # 'payment_reference': len(payment_refs) == 1 and payment_refs.pop() or False,
+ # })
+ # new_invoice_vals_list.append(ref_invoice_vals)
+ # invoice_vals_list = new_invoice_vals_list
+ #
+ # # 3) Create invoices.
+ #
+ # # As part of the invoice creation, we make sure the sequence of multiple SO do not interfere
+ # # in a single invoice. Example:
+ # # SO 1:
+ # # - Section A (sequence: 10)
+ # # - Product A (sequence: 11)
+ # # SO 2:
+ # # - Section B (sequence: 10)
+ # # - Product B (sequence: 11)
+ # #
+ # # If SO 1 & 2 are grouped in the same invoice, the result will be:
+ # # - Section A (sequence: 10)
+ # # - Section B (sequence: 10)
+ # # - Product A (sequence: 11)
+ # # - Product B (sequence: 11)
+ # #
+ # # Resequencing should be safe, however we resequence only if there are less invoices than
+ # # orders, meaning a grouping might have been done. This could also mean that only a part
+ # # of the selected SO are invoiceable, but resequencing in this case shouldn't be an issue.
+ # if len(invoice_vals_list) < len(self):
+ # SaleOrderLine = self.env['sale.order.line']
+ # for invoice in invoice_vals_list:
+ # sequence = 1
+ # for line in invoice['invoice_line_ids']:
+ # line[2]['sequence'] = SaleOrderLine._get_invoice_line_sequence(new=sequence, old=line[2]['sequence'])
+ # sequence += 1
+ #
+ # # Manage the creation of invoices in sudo because a salesperson must be able to generate an invoice from a
+ # # sale order without "billing" access rights. However, he should not be able to create an invoice from scratch.
+ # moves = self.env['account.move'].sudo().with_context(default_move_type='out_invoice').create(invoice_vals_list)
+ #
+ # # 4) Some moves might actually be refunds: convert them if the total amount is negative
+ # # We do this after the moves have been created since we need taxes, etc. to know if the total
+ # # is actually negative or not
+ # if final:
+ # moves.sudo().filtered(lambda m: m.amount_total < 0).action_switch_invoice_into_refund_credit_note()
+ # for move in moves:
+ # move.message_post_with_view('mail.message_origin_link',
+ # values={'self': move, 'origin': move.line_ids.mapped('sale_line_ids.order_id')},
+ # subtype_id=self.env.ref('mail.mt_note').id
+ # )
+ # return moves \ No newline at end of file
diff --git a/indoteknik_custom/models/sale_monitoring.py b/indoteknik_custom/models/sale_monitoring.py
index a837a6bc..c7f4368a 100755
--- a/indoteknik_custom/models/sale_monitoring.py
+++ b/indoteknik_custom/models/sale_monitoring.py
@@ -43,7 +43,7 @@ class SaleMonitoring(models.Model):
WHEN SUM(qty_to_delivered) < SUM(qty_so) and SUM(qty_to_delivered) > 0 THEN 'KIRIM SISANYA'
ELSE 'Belum Invoiced'
END AS status,
- get_po_number(smd.sale_order_id) as po_number
+ 0 as po_number
FROM sale_monitoring_detail smd
GROUP BY smd.date_order, smd.sale_order_id, smd.partner_id, smd.user_id
)
diff --git a/indoteknik_custom/models/sale_monitoring_detail.py b/indoteknik_custom/models/sale_monitoring_detail.py
index d766cecd..ade67b09 100755
--- a/indoteknik_custom/models/sale_monitoring_detail.py
+++ b/indoteknik_custom/models/sale_monitoring_detail.py
@@ -44,7 +44,7 @@ class SaleMonitoringDetail(models.Model):
p.id AS product_id,
sol.product_uom_qty AS qty_so,
sol.qty_delivered AS qty_so_delivered,
- get_qty_to_delivered(sol.id) as qty_to_delivered,
+ 0 as qty_to_delivered,
sol.qty_invoiced AS qty_so_invoiced,
so.date_order AS date_order,
get_qty_po(so.id, sol.product_id) AS qty_po,
diff --git a/indoteknik_custom/models/x_manufactures.py b/indoteknik_custom/models/x_manufactures.py
index e07f724e..b32d257d 100755
--- a/indoteknik_custom/models/x_manufactures.py
+++ b/indoteknik_custom/models/x_manufactures.py
@@ -10,6 +10,8 @@ class XManufactures(models.Model):
x_description = fields.Html(string="Description")
x_logo_manufacture = fields.Binary(string="Logo Manufacture")
x_logo_manufacture_128 = fields.Image("Image 128", related="x_logo_manufacture", max_width=128, max_height=128, store=True)
+ image_promotion_1 = fields.Binary(string="Promotion Image 1")
+ image_promotion_2 = fields.Binary(string="Promotion Image 2")
x_manufacture_level = fields.Selection([
('prioritas', 'Prioritas'),
('gold', 'Gold'),
diff --git a/indoteknik_custom/report/report_deliveryslip2.xml b/indoteknik_custom/report/report_deliveryslip2.xml
new file mode 100644
index 00000000..735cee52
--- /dev/null
+++ b/indoteknik_custom/report/report_deliveryslip2.xml
@@ -0,0 +1,214 @@
+<odoo>
+ <data>
+ <record id="paperformat_euro_lowmargin" model="report.paperformat">
+ <field name="name">A5 Delivery Slip</field>
+ <field name="default" eval="True"/>
+ <field name="format">A5</field>
+ <field name="page_height">0</field>
+ <field name="page_width">0</field>
+ <field name="orientation">Landscape</field>
+ <field name="margin_top">32.00</field>
+ <field name="margin_bottom">8.00</field>
+ <field name="margin_left">5.00</field>
+ <field name="margin_right">5.00</field>
+ <field name="header_line" eval="False"/>
+ <field name="header_spacing">30</field>
+ <field name="dpi">90</field>
+ </record>
+ <record id="stock.action_report_delivery" model="ir.actions.report">
+ <field name="paperformat_id" ref="paperformat_euro_lowmargin"/>
+ </record>
+ </data>
+
+ <template id="report_delivery_document3" inherit_id="stock.report_delivery_document">
+ <xpath expr="//t[@t-call='web.external_layout']" position="replace">
+ <t t-call="web.external_layout">
+ <t t-set="o" t-value="o.with_context(lang=o.partner_id.lang)" />
+ <!-- <t t-set="partner" t-value="o.partner_id or (o.move_lines and o.move_lines[0].partner_id) or False"/>-->
+ <!-- <t t-if="partner" name="partner_header">-->
+ <!-- <t t-set="address">-->
+ <!-- <span t-field="o.name"/>-->
+ <!-- <div t-esc="partner"-->
+ <!-- t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True}'/>-->
+ <!-- </t>-->
+ <!-- </t>-->
+
+ <style>
+ td {
+ vertical-align: text-top;
+ }
+ .header {
+ position: fixed;
+ width: 100%;
+ top: 8px;
+ }
+ </style>
+ <div class="header">
+ <div style="margin-bottom: 16px;">
+ <span>PT. Indoteknik Dotcom Gemilang</span>
+ <img style="float: right;" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('Code128', o.name, 300, 50)" alt="Barcode"/>
+ </div>
+ <table>
+ <tr>
+ <td width="50%" style="padding: 0 24px 0 0; ">
+ <p class="mb-1">Kepada:</p>
+ <p t-field="o.partner_id.name"/>
+ <p t-field="o.partner_id.street"/>
+ <p t-field="o.partner_id.mobile"/>
+ </td>
+ <td width="50%" style="padding: 0 0 0 24px;">
+ <p>No. Invoice: <b><span t-field="o.origin"/></b></p>
+ <p>No. DO: <b><span t-field="o.name"/></b></p>
+ </td>
+ </tr>
+ </table>
+ </div>
+
+ <div class="page">
+ <table class="table table-sm" t-if="o.state!='done'" name="stock_move_table">
+ <thead>
+ <tr>
+ <th name="th_sm_product"><strong>Product</strong></th>
+ <th name="th_sm_quantity"><strong>Quantity</strong></th>
+ </tr>
+ </thead>
+ <tbody>
+ <t t-set="lines" t-value="o.move_lines.filtered(lambda x: x.product_uom_qty)"/>
+ <tr t-foreach="lines" t-as="move">
+ <td>
+ <span t-field="move.product_id"/>
+ <p t-if="move.description_picking != move.product_id.name">
+ <span t-field="move.description_picking"/>
+ </p>
+ </td>
+ <td style="text-align: right;">
+ <span t-field="move.product_uom_qty"/>
+ <span t-field="move.product_uom"/>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <table class="table table-sm mt48" t-if="o.move_line_ids and o.state=='done'" name="stock_move_line_table">
+ <t t-set="has_serial_number" t-value="False"/>
+ <t t-set="has_serial_number" t-value="o.move_line_ids.mapped('lot_id')" groups="stock.group_lot_on_delivery_slip"/>
+ <thead>
+ <tr>
+ <th name="th_sml_product"><strong>Product</strong></th>
+ <t name="lot_serial" t-if="has_serial_number">
+ <th>
+ Lot/Serial Number
+ </th>
+ </t>
+ <th name="th_sml_quantity" class="text-center"><strong>Quantity</strong></th>
+ </tr>
+ </thead>
+ <tbody>
+ <!-- This part gets complicated with different use cases (additional use cases in extensions of this report):
+ 1. If serial numbers are used and set to print on delivery slip => print lines as is, otherwise group them by overlapping
+ product + description + uom combinations
+ 2. If any packages are assigned => split products up by package (or non-package) and then apply use case 1 -->
+ <!-- If has destination packages => create sections of corresponding products -->
+ <t t-if="o.has_packages" name="has_packages">
+ <t t-set="packages" t-value="o.move_line_ids.mapped('result_package_id')"/>
+ <t t-foreach="packages" t-as="package">
+ <t t-call="stock.stock_report_delivery_package_section_line"/>
+ <t t-set="package_move_lines" t-value="o.move_line_ids.filtered(lambda l: l.result_package_id == package)"/>
+ <!-- If printing lots/serial numbers => keep products in original lines -->
+ <t t-if="has_serial_number">
+ <tr t-foreach="package_move_lines" t-as="move_line">
+ <t t-call="stock.stock_report_delivery_has_serial_move_line"/>
+ </tr>
+ </t>
+ <!-- If not printing lots/serial numbers => merge lines with same product+description+uom -->
+ <t t-else="">
+ <t t-set="aggregated_lines" t-value="package_move_lines._get_aggregated_product_quantities()"/>
+ <t t-call="stock.stock_report_delivery_aggregated_move_lines"/>
+ </t>
+ </t>
+ <!-- Make sure we do another section for package-less products if they exist -->
+ <t t-set="move_lines" t-value="o.move_line_ids.filtered(lambda l: not l.result_package_id)"/>
+ <t t-if="move_lines" name="no_package_move_lines">
+ <t t-call="stock.stock_report_delivery_no_package_section_line" name="no_package_section"/>
+ <t t-if="has_serial_number">
+ <tr t-foreach="move_lines" t-as="move_line">
+ <t t-call="stock.stock_report_delivery_has_serial_move_line"/>
+ </tr>
+ </t>
+ <t t-else="">
+ <t t-set="aggregated_lines" t-value="move_lines._get_aggregated_product_quantities()"/>
+ <t t-if="aggregated_lines">
+ <t t-call="stock.stock_report_delivery_aggregated_move_lines"/>
+ </t>
+ </t>
+ </t>
+ </t>
+ <!-- No destination packages -->
+ <t t-else="">
+ <!-- If printing lots/serial numbers => keep products in original lines -->
+ <t t-if="has_serial_number">
+ <tr t-foreach="o.move_line_ids" t-as="move_line">
+ <t t-call="stock.stock_report_delivery_has_serial_move_line"/>
+ </tr>
+ </t>
+ <!-- If not printing lots/serial numbers => merge lines with same product -->
+ <t t-else="" name="aggregated_move_lines">
+ <t t-set="aggregated_lines" t-value="o.move_line_ids._get_aggregated_product_quantities()"/>
+ <t t-call="stock.stock_report_delivery_aggregated_move_lines"/>
+ </t>
+ </t>
+ </tbody>
+ </table>
+ <t t-set="backorders" t-value="o.backorder_ids.filtered(lambda x: x.state not in ('done', 'cancel'))"/>
+ <t t-if="o.backorder_ids and backorders">
+ <p>
+ <span>All items couldn't be shipped, the following items will be shipped as soon as they become available.</span>
+ </p>
+ <table class="table table-sm" name="stock_backorder_table">
+ <thead>
+ <tr>
+ <th name="th_sb_product"><strong>Product</strong></th>
+ <th name="th_sb_quantity" class="text-center"><strong>Quantity</strong></th>
+ </tr>
+ </thead>
+ <tbody>
+ <t t-foreach="backorders" t-as="backorder">
+ <t t-set="bo_lines" t-value="backorder.move_lines.filtered(lambda x: x.product_uom_qty)"/>
+ <tr t-foreach="bo_lines" t-as="bo_line">
+ <td>
+ <span t-field="bo_line.product_id"/>
+ <p t-if="bo_line.description_picking != bo_line.product_id.name">
+ <span t-field="bo_line.description_picking"/>
+ </p>
+ </td>
+ <td class="text-center">
+ <span t-field="bo_line.product_uom_qty"/>
+ <span t-field="bo_line.product_uom"/>
+ </td>
+ </tr>
+ </t>
+ </tbody>
+ </table>
+ </t>
+
+ <div t-if="o.signature" class="mt32 ml64 mr4" name="signature">
+ <div class="offset-8">
+ <strong>Signature</strong>
+ </div>
+ <div class="offset-8">
+ <img t-att-src="image_data_uri(o.signature)" style="max-height: 4cm; max-width: 8cm;"/>
+ </div>
+ <div class="offset-8 text-center">
+ <p t-field="o.partner_id.name"/>
+ </div>
+ </div>
+ </div>
+ </t>
+ </xpath>
+<!-- <xpath expr="//div/h2" position="replace">-->
+<!-- <div style="margin: 16px 0;">-->
+<!-- <span class="h2" t-field="o.name"/>-->
+<!-- <img style="float: right;" t-att-src="'/report/barcode/?type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('Code128', o.name, 300, 50)" alt="Barcode"/>-->
+<!-- </div>-->
+<!-- </xpath>-->
+ </template>
+</odoo>
diff --git a/indoteknik_custom/static/src/js/delivery_order.js b/indoteknik_custom/static/src/js/delivery_order.js
new file mode 100644
index 00000000..565610ad
--- /dev/null
+++ b/indoteknik_custom/static/src/js/delivery_order.js
@@ -0,0 +1,44 @@
+odoo.define('indoteknik_custom.FieldChar', function (require) {
+ "use strict";
+
+ console.log('12345');
+ var FieldChar = require('web.basic_fields').FieldChar;
+ var registry = require('web.field_registry');
+
+ var FieldCharCustom = FieldChar.extend({
+ _onKeyup: function (ev) {
+ console.log('keyuppp');
+ if (ev.which === $.ui.keyCode.ENTER) {
+ console.log('abc');
+ }
+ this._super.apply(this, arguments);
+ },
+ });
+
+ registry.add('name', FieldCharCustom);
+
+ return FieldCharCustom;
+
+// var core = require('web.core');
+// var _t = core._t;
+// var FormView = require('web.FormView');
+
+// FormView.include({
+// init: function (parent, model, renderer, params) {
+// var self = this;
+// self._super.apply(self.arguments);
+// },
+// onchange_name: function () {
+// this._super();
+// console.log('bbbb');
+// if (this.model === 'delivery.order') {
+// this.$('#name').on('keyup', function () {
+// console.log('aaa')
+// });
+// }
+// }
+// });
+//
+// core.action_registry.add('indoteknik_custom.FieldChar', FormView);
+// return FormView;
+}); \ No newline at end of file
diff --git a/indoteknik_custom/views/x_manufactures.xml b/indoteknik_custom/views/x_manufactures.xml
index afecea9b..e7e1b85d 100755
--- a/indoteknik_custom/views/x_manufactures.xml
+++ b/indoteknik_custom/views/x_manufactures.xml
@@ -43,6 +43,8 @@
</group>
<group>
<field name="x_logo_manufacture" widget="image"/>
+ <field name="image_promotion_1" widget="image" width="80" />
+ <field name="image_promotion_2" widget="image" width="80" />
</group>
</group>
<notebook>