summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorit-fixcomart <it@fixcomart.co.id>2024-10-25 08:47:36 +0700
committerit-fixcomart <it@fixcomart.co.id>2024-10-25 08:47:36 +0700
commit9fa80c62cdffec5b91aaf2a2899d70d0f507c2e4 (patch)
tree93ae8d82f774345f650bc8d072dea759c6e33363
parenta21c5fe37529b2d2259d3b86d8e98730b2bc8513 (diff)
parenta7be93f4825967807f12e6bfbebcf090af8500fa (diff)
Merge branch 'production' into iman/switch-account
# Conflicts: # indoteknik_api/controllers/api_v1/user.py # indoteknik_custom/models/user_company_request.py
-rw-r--r--indoteknik_api/controllers/api_v1/__init__.py3
-rw-r--r--indoteknik_api/controllers/api_v1/city.py12
-rw-r--r--indoteknik_api/controllers/api_v1/partner.py7
-rw-r--r--indoteknik_api/controllers/api_v1/product.py1
-rw-r--r--indoteknik_api/controllers/api_v1/state.py24
-rw-r--r--indoteknik_api/controllers/api_v1/user.py43
-rw-r--r--indoteknik_api/models/res_users.py9
-rwxr-xr-xindoteknik_custom/__manifest__.py2
-rwxr-xr-xindoteknik_custom/models/__init__.py3
-rw-r--r--indoteknik_custom/models/account_move_due_extension.py7
-rw-r--r--indoteknik_custom/models/approval_unreserve.py7
-rwxr-xr-xindoteknik_custom/models/crm_lead.py91
-rw-r--r--indoteknik_custom/models/find_page.py121
-rw-r--r--indoteknik_custom/models/partner.py7
-rwxr-xr-xindoteknik_custom/models/product_template.py8
-rwxr-xr-xindoteknik_custom/models/purchase_order.py19
-rwxr-xr-xindoteknik_custom/models/purchase_order_line.py10
-rw-r--r--indoteknik_custom/models/purchasing_job.py90
-rw-r--r--indoteknik_custom/models/report_stock_forecasted.py1
-rw-r--r--indoteknik_custom/models/requisition.py27
-rw-r--r--indoteknik_custom/models/res_partner.py4
-rwxr-xr-xindoteknik_custom/models/sale_order.py208
-rw-r--r--indoteknik_custom/models/sale_order_line.py12
-rw-r--r--indoteknik_custom/models/solr/product_product.py12
-rw-r--r--indoteknik_custom/models/solr/product_template.py16
-rw-r--r--indoteknik_custom/models/stock_picking.py100
-rw-r--r--indoteknik_custom/models/user_company_request.py33
-rw-r--r--indoteknik_custom/models/vendor_approval.py73
-rw-r--r--indoteknik_custom/models/website_user_cart.py9
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv5
-rw-r--r--indoteknik_custom/views/account_move_views.xml8
-rw-r--r--indoteknik_custom/views/dunning_run.xml1
-rw-r--r--indoteknik_custom/views/find_page.xml70
-rw-r--r--indoteknik_custom/views/ir_sequence.xml10
-rwxr-xr-xindoteknik_custom/views/product_template.xml15
-rwxr-xr-xindoteknik_custom/views/purchase_order.xml9
-rw-r--r--indoteknik_custom/views/requisition.xml1
-rw-r--r--indoteknik_custom/views/res_partner.xml9
-rwxr-xr-xindoteknik_custom/views/sale_order.xml14
-rw-r--r--indoteknik_custom/views/stock_picking.xml4
-rw-r--r--indoteknik_custom/views/user_company_request.xml5
-rw-r--r--indoteknik_custom/views/vendor_approval.xml106
-rwxr-xr-xindoteknik_custom/views/vit_kota.xml2
43 files changed, 1058 insertions, 160 deletions
diff --git a/indoteknik_api/controllers/api_v1/__init__.py b/indoteknik_api/controllers/api_v1/__init__.py
index 2d774071..6f27eec5 100644
--- a/indoteknik_api/controllers/api_v1/__init__.py
+++ b/indoteknik_api/controllers/api_v1/__init__.py
@@ -28,4 +28,5 @@ from . import midtrans
from . import wati
from . import courier
from . import voucher
-from . import stock_picking \ No newline at end of file
+from . import stock_picking
+from . import state \ No newline at end of file
diff --git a/indoteknik_api/controllers/api_v1/city.py b/indoteknik_api/controllers/api_v1/city.py
index 6e0e3edb..afe92c4a 100644
--- a/indoteknik_api/controllers/api_v1/city.py
+++ b/indoteknik_api/controllers/api_v1/city.py
@@ -15,10 +15,16 @@ class City(controller.Controller):
name = '%' + name.replace(' ', '%') + '%'
parameters.append(('name', 'ilike', name))
- cities = request.env['vit.kota'].search(parameters)
+ state_id = kw.get('state_id')
+ if state_id:
+ parameters.append(('state_id', '=', int(state_id)))
+
+ districts = request.env['vit.kota'].search(parameters)
data = []
- for city in cities:
- data.append({ 'id': city.id, 'name': city.name })
+ for district in districts:
+ data.append({ 'id': district.id, 'name': district.name })
return self.response(data)
+
+
diff --git a/indoteknik_api/controllers/api_v1/partner.py b/indoteknik_api/controllers/api_v1/partner.py
index d1a41739..4189330c 100644
--- a/indoteknik_api/controllers/api_v1/partner.py
+++ b/indoteknik_api/controllers/api_v1/partner.py
@@ -13,6 +13,7 @@ class Partner(controller.Controller):
partner_child_ids = [x['id'] for x in partner.child_ids] + [partner.id]
if partner.parent_id:
partner_child_ids = [x['id'] for x in partner.parent_id.child_ids]
+ partner_child_ids += [partner.parent_id.id]
return partner_child_ids
@http.route(prefix + 'partner/<id>/list/site', auth='public', methods=['GET', 'OPTIONS'])
@@ -68,6 +69,7 @@ class Partner(controller.Controller):
'mobile': ['required'],
'phone': [''],
'street': ['required'],
+ 'state_id': ['required', 'number', 'alias:state_id'],
'city_id': ['required', 'number', 'alias:kota_id'],
'district_id': ['number', 'alias:kecamatan_id'],
'sub_district_id': ['number', 'alias:kelurahan_id', 'exclude_if_null'],
@@ -81,7 +83,7 @@ class Partner(controller.Controller):
partner = request.env[self._name].search([('id', '=', params['value']['id'])], limit=1)
if not partner:
return self.response(code=404, description='User not found')
-
+
partner.write(params['value'])
return self.response({
@@ -99,10 +101,11 @@ class Partner(controller.Controller):
'mobile': ['required'],
'phone': [''],
'street': ['required'],
+ 'state_id': ['required', 'number', 'alias:state_id'],
'city_id': ['required', 'number', 'alias:kota_id'],
'district_id': ['number', 'alias:kecamatan_id'],
'sub_district_id': ['number', 'alias:kelurahan_id', 'exclude_if_null'],
- 'zip': ['required'],
+ 'zip': ['required']
})
if not params['valid']:
diff --git a/indoteknik_api/controllers/api_v1/product.py b/indoteknik_api/controllers/api_v1/product.py
index e779e623..9673b3ef 100644
--- a/indoteknik_api/controllers/api_v1/product.py
+++ b/indoteknik_api/controllers/api_v1/product.py
@@ -74,7 +74,6 @@ class Product(controller.Controller):
if qty_available > 0:
qty = qty_available + total_adem + total_excell
- sla_date = '1 Hari'
elif qty_altama > 0 or qty_vendor > 0:
qty = total_adem if qty_altama > 0 else total_excell
sla_date = '2-4 Hari'
diff --git a/indoteknik_api/controllers/api_v1/state.py b/indoteknik_api/controllers/api_v1/state.py
new file mode 100644
index 00000000..598ef70b
--- /dev/null
+++ b/indoteknik_api/controllers/api_v1/state.py
@@ -0,0 +1,24 @@
+from .. import controller
+from odoo import http
+from odoo.http import request
+
+class District(controller.Controller):
+ prefix = '/api/v1/'
+
+ @http.route(prefix + 'state', auth='public', methods=['GET', 'OPTIONS'])
+ @controller.Controller.must_authorized()
+ def get_state(self, **kw):
+ parameters = []
+
+ name = kw.get('name')
+ if name:
+ name = '%' + name.replace(' ', '%') + '%'
+ parameters.append(('name', 'ilike', name))
+
+ states = request.env['res.country.state'].search(parameters)
+ data = []
+ for state in states:
+ data.append({ 'id': state.id, 'name': state.name })
+
+ return self.response(data)
+
diff --git a/indoteknik_api/controllers/api_v1/user.py b/indoteknik_api/controllers/api_v1/user.py
index c490500c..e06a099f 100644
--- a/indoteknik_api/controllers/api_v1/user.py
+++ b/indoteknik_api/controllers/api_v1/user.py
@@ -94,7 +94,13 @@ class User(controller.Controller):
user = request.env['res.users'].create(user_data)
user.partner_id.email = email
-
+ user.partner_id.customer_type = 'nonpkp'
+ user.partner_id.npwp = '00.000.000.0-000.000'
+ user.partner_id.sppkp = '-'
+ user.partner_id.nama_wajib_pajak = user.name
+ user.partner_id.user_id = 3222
+ user.partner_id.property_account_receivable_id = 395
+ user.partner_id.property_account_payable_id = 438
data = {
'is_auth': True,
'user': self.response_with_token(user)
@@ -149,6 +155,7 @@ class User(controller.Controller):
'name': name,
'login': email,
'mobile': phone,
+ 'phone': phone,
'password': password,
'active': False,
'sel_groups_1_9_10': 9
@@ -158,20 +165,26 @@ class User(controller.Controller):
user.partner_id.email = email
user.partner_id.mobile = phone
- if type_acc == 'business':
- parameter = [
- ('company_type', '=', 'company'),
- ('name', 'ilike', business_name)
- ]
- match_company = request.env['res.partner'].search(parameter, limit=1)
- match_ratio = 0
- if match_company:
- match_ratio = SequenceMatcher(None, match_company.name, business_name).ratio()
- if match_ratio > 0.8:
+ if type_acc == 'business' and business_name:
+ # Eksekusi query SQL menggunakan Levenshtein distance
+ query = """
+ SELECT name, levenshtein(name::text, %s) AS distance
+ FROM res_partner
+ WHERE levenshtein(name::text, %s) < 3
+ ORDER BY distance ASC
+ """
+ params = (business_name, business_name)
+ request.env.cr.execute(query, params)
+ result = request.env.cr.fetchone()
+
+ if result:
+ match_company_name = result[0]
+ match_company_id = result[2]
+
# Create a user company request
request.env['user.company.request'].create({
'user_id': user.partner_id.id,
- 'user_company_id': match_company.id,
+ 'user_company_id': match_company_id,
'user_input': business_name
})
else:
@@ -235,7 +248,7 @@ class User(controller.Controller):
if type_acc == 'individu':
user.partner_id.customer_type = 'nonpkp'
- user.partner_id.npwp = '0.000.000.0-000.000'
+ user.partner_id.npwp = '00.000.000.0-000.000'
user.partner_id.sppkp = '-'
user.partner_id.nama_wajib_pajak = name
user.partner_id.user_id = 3222
@@ -255,7 +268,7 @@ class User(controller.Controller):
'activation_request': False,
'reason': None
}
-
+
user = self.get_user_by_email(email)
if not user:
response['reason'] = 'NOT_FOUND'
@@ -769,4 +782,4 @@ class User(controller.Controller):
response['status'] = False
# If no attachment is found, return status False
- return self.response(response)
+ return self.response(response) \ No newline at end of file
diff --git a/indoteknik_api/models/res_users.py b/indoteknik_api/models/res_users.py
index 195d786f..f7fdc4ed 100644
--- a/indoteknik_api/models/res_users.py
+++ b/indoteknik_api/models/res_users.py
@@ -48,6 +48,7 @@ class ResUsers(models.Model):
'street': user.street or '',
'street2': user.street2 or '',
'city': None,
+ 'state_id': 0,
'district': None,
'sub_district': None,
'zip': user.zip or '',
@@ -62,12 +63,18 @@ class ResUsers(models.Model):
'companyType': user.customer_type or 'nonpkp',
}
+ if user.state_id:
+ data['state_id'] = {
+ 'id': user.state_id.id,
+ 'name': user.state_id.name
+ } or None
+
if user.kota_id:
data['city'] = {
'id': user.kota_id.id,
'name': user.kota_id.name
} or None
-
+
if user.kecamatan_id:
data['district'] = {
'id': user.kecamatan_id.id,
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py
index e1a67592..c8a658b5 100755
--- a/indoteknik_custom/__manifest__.py
+++ b/indoteknik_custom/__manifest__.py
@@ -143,6 +143,8 @@
'views/partner_payment_term.xml',
'views/vendor_payment_term.xml',
'views/approval_unreserve.xml',
+ 'views/vendor_approval.xml',
+ 'views/find_page.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 3d700ce0..e62fbb4a 100755
--- a/indoteknik_custom/models/__init__.py
+++ b/indoteknik_custom/models/__init__.py
@@ -128,3 +128,6 @@ from . import sales_order_reject
from . import approval_date_doc
from . import account_tax
from . import approval_unreserve
+from . import vendor_approval
+from . import partner
+from . import find_page
diff --git a/indoteknik_custom/models/account_move_due_extension.py b/indoteknik_custom/models/account_move_due_extension.py
index 23f8888c..6fc58cdd 100644
--- a/indoteknik_custom/models/account_move_due_extension.py
+++ b/indoteknik_custom/models/account_move_due_extension.py
@@ -1,6 +1,6 @@
from odoo import models, api, fields
from odoo.exceptions import AccessError, UserError, ValidationError
-from datetime import timedelta, date
+from datetime import timedelta, date, datetime
import logging
_logger = logging.getLogger(__name__)
@@ -31,7 +31,8 @@ class DueExtension(models.Model):
('21', '21 Hari'),
], string='Day Extension', help='Menambah Due Date yang sudah limit dari hari ini', tracking=True)
counter = fields.Integer(string="Counter", compute='_compute_counter')
-
+ approve_by = fields.Many2one('res.users', string="Approve By", readonly=True)
+ date_approve = fields.Datetime(string="Date Approve", readonly=True)
def _compute_counter(self):
for due in self:
due.counter = due.partner_id.counter
@@ -96,6 +97,8 @@ class DueExtension(models.Model):
sales.action_confirm()
self.order_id.due_id = self.id
+ self.approve_by = self.env.user.id
+ self.date_approve = datetime.utcnow()
template = self.env.ref('indoteknik_custom.mail_template_due_extension_approve')
template.send_mail(self.id, force_send=True)
diff --git a/indoteknik_custom/models/approval_unreserve.py b/indoteknik_custom/models/approval_unreserve.py
index 88409c37..07ddda1f 100644
--- a/indoteknik_custom/models/approval_unreserve.py
+++ b/indoteknik_custom/models/approval_unreserve.py
@@ -31,12 +31,12 @@ class ApprovalUnreserve(models.Model):
if not self.picking_id:
raise ValidationError("Picking is required")
- stock_move = self.env['stock.move'].search([('picking_id', '=', self.picking_id.id), ('state', '=', 'assigned')])
+ stock_move = self.env['stock.move'].search([('picking_id', '=', self.picking_id.id), ('state', 'in', ['assigned', 'partially_available'])])
if not stock_move:
raise ValidationError("Picking is not found")
- for move in stock_move:
+ for move in stock_move:
self.approval_line.create({
'approval_id': self.id,
'move_id': move.id
@@ -68,7 +68,7 @@ class ApprovalUnreserve(models.Model):
if not move:
raise UserError("Product tidak ada di destination picking")
- qty_unreserve = line.unreserve_qty + move.forecast_availability
+ qty_unreserve = line.unreserve_qty + move.reserved_availability
if move.product_uom_qty < qty_unreserve:
raise UserError("Quantity yang di unreserve melebihi quantity yang ada")
@@ -86,6 +86,7 @@ class ApprovalUnreserve(models.Model):
})
# Trigger the unreserve function
self._trigger_unreserve()
+ self.picking_id.check_state_reserve()
def action_reject(self, reason):
if self.env.user.id != self.user_id.id:
diff --git a/indoteknik_custom/models/crm_lead.py b/indoteknik_custom/models/crm_lead.py
index 9ffd607c..de2bdb12 100755
--- a/indoteknik_custom/models/crm_lead.py
+++ b/indoteknik_custom/models/crm_lead.py
@@ -2,6 +2,7 @@ from odoo import fields, models, api
import logging
import random
from odoo.exceptions import AccessError, UserError, ValidationError
+from datetime import datetime, timedelta
_logger = logging.getLogger(__name__)
@@ -97,4 +98,92 @@ class CrmLead(models.Model):
lead.user_id = salesperson_id
-
+ def _update_pipeline(self, delta=48, limit=100):
+ # Get the current time
+ current_time = datetime.now()
+
+ # Calculate the time 24 hours ago
+ time_48_hours_ago = current_time - timedelta(hours=delta)
+
+ # Define the allowed states
+ allowed_states = ['sale', 'done']
+
+ # Search for sale orders with date_order greater than 24 hours ago and opportunity_id is null
+ orders = self.env['sale.order'].search([
+ ('write_date', '>=', time_48_hours_ago),
+ ('opportunity_id', '!=', False),
+ ('state', 'in', allowed_states)
+ ], limit=limit)
+ for order in orders:
+ order.opportunity_id.stage_id = 4
+ _logger.info('finish order stage pipeline %s' % order.id)
+
+ def _cancel_pipeline(self, delta=48, limit=100):
+ # Get the current time
+ current_time = datetime.now()
+
+ # Calculate the time 24 hours ago
+ time_48_hours_ago = current_time - timedelta(hours=delta)
+
+ # Define the allowed states
+ allowed_states = ['cancel']
+
+ # Search for sale orders with date_order greater than 24 hours ago and opportunity_id is null
+ orders = self.env['sale.order'].search([
+ ('write_date', '>=', time_48_hours_ago),
+ ('opportunity_id', '!=', False),
+ ('state', 'in', allowed_states)
+ ], limit=limit)
+ for order in orders:
+ order.opportunity_id.stage_id = 7
+ _logger.info('cancel order stage pipeline %s' % order.id)
+
+ def _convert_to_pipeline(self, delta=48, limit=100):
+ # Get the current time
+ current_time = datetime.now()
+
+ # Calculate the time 24 hours ago
+ time_48_hours_ago = current_time - timedelta(hours=delta)
+
+ # Define the allowed states
+ allowed_states = ['draft', 'done', 'sale', 'sent', 'cancel']
+
+ # Search for sale orders with date_order greater than 24 hours ago and opportunity_id is null
+ orders = self.env['sale.order'].search([
+ ('write_date', '>=', time_48_hours_ago),
+ ('opportunity_id', '=', False),
+ ('state', 'in', allowed_states)
+ ], limit=limit)
+ # stage
+ # 1 potensi baru, 2 proses quotation, 3 proses lain visit, 4 proses berhasil, 5 proses negosiasi, 7 tidak terpakai / gagal
+ for order in orders:
+ # stage_id = 2
+ if order.state == 'sale' or order.state == 'done':
+ stage_id = 4
+ elif order.state == 'sent':
+ stage_id = 5
+ elif order.state == 'cancel':
+ stage_id = 7
+ else:
+ stage_id = 2
+ crm_lead = self.env['crm.lead'].create([{
+ 'email_normalized': order.email,
+ 'name': order.name,
+ 'user_id': order.user_id.id,
+ 'company_id': 1,
+ 'type': 'opportunity',
+ 'priority': 0,
+ 'team_id': order.team_id.id,
+ 'stage_id': stage_id,
+ 'expected_revenue': order.amount_untaxed,
+ 'partner_id': order.partner_id.parent_id.id or order.partner_id.id,
+ 'contact_name': order.partner_id.name,
+ 'partner_name': order.partner_id.parent_id.name or order.partner_id.name,
+ 'phone': order.partner_id.mobile,
+ 'street': order.partner_id.street,
+ 'street2': order.partner_id.street2,
+ 'zip': order.partner_id.zip,
+ 'order_id': order.id
+ }])
+ order.opportunity_id = crm_lead.id
+ _logger.info('convert order to opportunity %s' % crm_lead.id)
diff --git a/indoteknik_custom/models/find_page.py b/indoteknik_custom/models/find_page.py
new file mode 100644
index 00000000..467e30d1
--- /dev/null
+++ b/indoteknik_custom/models/find_page.py
@@ -0,0 +1,121 @@
+from odoo import fields, models, api, tools, _
+import logging
+import re
+import pysolr
+
+_logger = logging.getLogger(__name__)
+_cat_brand_solr = pysolr.Solr('http://10.148.0.5:8983/solr/url_category_brand/', always_commit=True, timeout=30)
+# _cat_brand_solr_dev = pysolr.Solr('http://127.0.0.1:8983/solr/url_category_brand/', always_commit=True, timeout=30)
+
+
+class BrandProductCategory(models.Model):
+ _name = 'v.brand.product.category'
+ _auto = False
+ _rec_name = 'brand_id'
+ brand_id = fields.Many2one('x_manufactures', string='Brand')
+ category_id = fields.Many2one('product.public.category', string='Category')
+
+ def init(self):
+ tools.drop_view_if_exists(self.env.cr, self._table)
+ self.env.cr.execute("""
+ CREATE OR REPLACE VIEW %s
+ AS select row_number() over(order by pt.x_manufacture) as id,
+ pt.x_manufacture as brand_id,
+ ppcptr.product_public_category_id as category_id
+ from product_template pt
+ join product_public_category_product_template_rel ppcptr on ppcptr.product_template_id = pt.id
+ join x_manufactures xm on xm.id = pt.x_manufacture
+ group by x_manufacture, ppcptr.product_public_category_id
+ """ % self._table)
+
+
+class FindPage(models.Model):
+ _name = 'web.find.page'
+ _inherit = ['mail.thread']
+ _rec_name = 'url'
+
+ brand_id = fields.Many2one('x_manufactures', string='Brand')
+ category_id = fields.Many2one('product.public.category', string='Category', help='Bisa semua level Category')
+ url = fields.Char(string='Url')
+
+ def _sync_to_solr(self, limit=10000):
+ urls = self.env['web.find.page'].search([])
+ documents = []
+ catch = {}
+ for url in urls:
+ try:
+ document = {
+ 'id': url.id,
+ 'category_id_i': url.category_id.id,
+ 'brand_id_i': url.brand_id.id,
+ 'url_s': url.url
+ }
+ documents.append(document)
+ catch = document
+ except Exception as e:
+ _logger.error('Failed to add document to Solr URL Category Brand: %s', e)
+ _logger.error('Document Data: %s', catch)
+ _cat_brand_solr.add(documents)
+ return True
+
+ def _get_category_hierarchy(self, category):
+ categories = []
+ current_category = category
+ while current_category:
+ categories.insert(0, current_category)
+ current_category = current_category.parent_id
+ return categories
+
+ def _generate_url(self):
+ categories = self.env['v.brand.product.category'].search([])
+
+ list_url = []
+ for category in categories:
+ category_hierarchy = self._get_category_hierarchy(category.category_id)
+
+ for level, cat in enumerate(reversed(category_hierarchy), start=1):
+ list_url.append(self._generate_mod_url(cat, category.brand_id))
+ # print(f"Level {level}: {cat.name} {category.brand_id.x_name}")
+
+ unique_list = []
+ for item in list_url:
+ if item not in unique_list:
+ unique_list.append(item)
+ count = 0
+ for item in unique_list:
+ self._create_find_page(item['url'], item['category_id'], item['brand_id'])
+ count += 1
+ print(f"Total categories processed: {count}")
+
+ def _create_find_page(self, url, category_id, brand_id):
+ param = {
+ 'url': url,
+ 'category_id': category_id,
+ 'brand_id': brand_id,
+ }
+ find_page = self.env['web.find.page'].create(param)
+ _logger.info('Created Web Find Page %s' % find_page.id)
+
+ def _generate_mod_url(self, category, brand):
+ # generate_url = 'https://indoteknik.com/shop/find/category-brand' example
+ cleaned_category = re.sub(r'[^\w\s]', '', category.name)
+ cleaned_brand = re.sub(r'[^\w\s]', '', brand.x_name)
+ cleaned_combined = cleaned_category+' '+cleaned_brand
+ cleaned_combined = cleaned_combined.replace(' ', '-')
+ cleaned_combined = cleaned_combined.replace('--', '-')
+ url = 'https://indoteknik.com/shop/find/'+cleaned_combined
+ url = url.lower()
+ result = {
+ 'url': url,
+ 'category_id': category.id,
+ 'brand_id': brand.id
+ }
+ # print(url)
+ # param = {
+ # 'brand_id': brand.id,
+ # 'category_id': category.id,
+ # 'url':''
+ # }
+ # self.env['web.find.page'].create()
+ # print(1)
+ return result
diff --git a/indoteknik_custom/models/partner.py b/indoteknik_custom/models/partner.py
new file mode 100644
index 00000000..46dc751a
--- /dev/null
+++ b/indoteknik_custom/models/partner.py
@@ -0,0 +1,7 @@
+from odoo import fields, models, api, _
+from odoo.exceptions import AccessError, UserError, ValidationError
+
+class kota(models.Model):
+ _inherit = 'vit.kota'
+
+ is_jabodetabek = fields.Boolean(string='Jabodetabek', default=False) \ No newline at end of file
diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py
index 031d1b5b..2ca4925b 100755
--- a/indoteknik_custom/models/product_template.py
+++ b/indoteknik_custom/models/product_template.py
@@ -5,6 +5,7 @@ import logging
import requests
import json
import re
+from bs4 import BeautifulSoup
_logger = logging.getLogger(__name__)
@@ -363,6 +364,7 @@ class ProductProduct(models.Model):
specification = fields.Char(string='Specification')
material = fields.Char(string='Material')
qty_onhand_bandengan = fields.Float(string='Onhand BU', compute='_get_qty_onhand_bandengan')
+ clean_website_description = fields.Char(string='Clean Website Description', compute='_get_clean_website_description')
qty_incoming_bandengan = fields.Float(string='Incoming BU', compute='_get_qty_incoming_bandengan')
qty_outgoing_bandengan = fields.Float(string='Outgoing BU', compute='_get_qty_outgoing_bandengan')
qty_available_bandengan = fields.Float(string='Available BU', compute='_get_qty_available_bandengan')
@@ -373,6 +375,11 @@ class ProductProduct(models.Model):
qty_sold = fields.Float(string='Sold Quantity', compute='_get_qty_sold')
short_spesification = fields.Char(string='Short Spesification')
+ def _get_clean_website_description(self):
+ for rec in self:
+ cleaned_desc = BeautifulSoup(self.website_description or '', "html.parser").get_text()
+ rec.clean_website_description = cleaned_desc
+
@api.constrains('name', 'internal_reference', 'x_manufacture')
def required_public_categ_ids(self):
for rec in self:
@@ -398,6 +405,7 @@ class ProductProduct(models.Model):
continue
if any(variant.active for variant in variants):
product_template.unpublished = False
+ variants.unpublished = False
def update_internal_reference_variants(self, limit=100):
variants = self.env['product.product'].search([
diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py
index 8a47482a..f924174a 100755
--- a/indoteknik_custom/models/purchase_order.py
+++ b/indoteknik_custom/models/purchase_order.py
@@ -70,6 +70,7 @@ class PurchaseOrder(models.Model):
bills_dp_id = fields.Many2one('account.move', string='Bills DP')
grand_total = fields.Monetary(string='Grand Total', help='Amount total + amount delivery', compute='_compute_grand_total')
total_margin_match = fields.Float(string='Total Margin Match', compute='_compute_total_margin_match')
+ approve_by = fields.Many2one('res.users', string='Approve By')
def _compute_total_margin_match(self):
for purchase in self:
@@ -503,12 +504,15 @@ class PurchaseOrder(models.Model):
current_time = datetime.now()
self.check_ppn_mix()
# self.check_data_vendor()
+
+ if self.amount_untaxed >= 50000000 and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'):
+ raise UserError("Hanya Merchandiser yang bisa approve")
- if self.total_percent_margin < self.total_so_percent_margin and not self.env.user.is_purchasing_manager and not self.env.user.is_leader:
- raise UserError("Beda Margin dengan Sales, harus approval Manager")
+ if self.total_percent_margin < self.total_so_percent_margin and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser') and not self.env.user.is_leader:
+ raise UserError("Beda Margin dengan Sales, harus approval Merchandise")
if not self.from_apo:
- if not self.sale_order_id and not self.env.user.is_purchasing_manager and not self.env.user.is_leader:
- raise UserError("Tidak ada link dengan SO, harus approval Manager")
+ if not self.sale_order_id and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser') and not self.env.user.is_leader:
+ raise UserError("Tidak ada link dengan SO, harus approval Merchandise")
send_email = False
self.add_product_to_pricelist()
@@ -530,7 +534,7 @@ class PurchaseOrder(models.Model):
self._send_mail()
if self.revisi_po:
- delta_time = current_time - timedelta(days=2)
+ delta_time = current_time - timedelta(days=1)
delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S')
self.date_approve = delta_time
@@ -538,6 +542,7 @@ class PurchaseOrder(models.Model):
self.po_status = 'menunggu'
self.calculate_line_no()
+ self.approve_by = self.env.user.id
# override date planned added with two days
leadtime = self.partner_id.leadtime
@@ -634,7 +639,9 @@ class PurchaseOrder(models.Model):
template.send_mail(self.id, force_send=True)
def po_approve(self):
- if self.env.user.is_leader or self.env.user.is_purchasing_manager:
+ if self.amount_untaxed >= 50000000 and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'):
+ raise UserError("Hanya Merchandiser yang bisa approve")
+ if self.env.user.is_leader or self.env.user.has_group('indoteknik_custom.group_role_merchandiser'):
raise UserError("Bisa langsung Confirm")
elif self.total_percent_margin == self.total_so_percent_margin and self.sale_order_id:
raise UserError("Bisa langsung Confirm")
diff --git a/indoteknik_custom/models/purchase_order_line.py b/indoteknik_custom/models/purchase_order_line.py
index 7af84b48..9e7d7e81 100755
--- a/indoteknik_custom/models/purchase_order_line.py
+++ b/indoteknik_custom/models/purchase_order_line.py
@@ -3,6 +3,7 @@ from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
import logging
from dateutil.relativedelta import relativedelta
+from bs4 import BeautifulSoup
from datetime import datetime
_logger = logging.getLogger(__name__)
@@ -37,11 +38,18 @@ class PurchaseOrderLine(models.Model):
is_ltc = fields.Boolean(string='Sudah di LTC', default=False, help='centang ini jika barang sudah di LTC')
note = fields.Char(string='Note')
sale_automatic_id = fields.Many2one('sale.order', string='SO')
-
+ image_small = fields.Binary("Product Image", related="product_id.image_1920")
+ clean_website_description_product = fields.Char(string='Clean Website Description Product', compute='_get_clean_website_description_product')
qty_reserved = fields.Float(string='Qty Reserved', compute='_compute_qty_reserved')
delete_line = fields.Boolean(string='Delete', default=False, help='centang ini jika anda ingin menghapus line ini')
is_edit_product_qty = fields.Boolean(string='Is Edit Product Qty', compute='_compute_is_edit_product_qty')
+ def _get_clean_website_description_product(self):
+ for line in self:
+ description = line.product_id.website_description
+ description_clean = BeautifulSoup(description or '', "html.parser").get_text()
+ line.clean_website_description_product = description_clean
+
def _prepare_stock_move_vals(self, picking, price_unit, product_uom_qty, product_uom):
self.ensure_one()
product = self.product_id.with_context(lang=self.order_id.dest_address_id.lang or self.env.user.lang)
diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py
index 6e4f239d..bf5ed8c4 100644
--- a/indoteknik_custom/models/purchasing_job.py
+++ b/indoteknik_custom/models/purchasing_job.py
@@ -43,73 +43,39 @@ class PurchasingJob(models.Model):
def init(self):
tools.drop_view_if_exists(self.env.cr, self._table)
self.env.cr.execute("""
- CREATE OR REPLACE VIEW %s AS (
- WITH latest_purchase_orders AS (
- SELECT
- pol.product_id,
- po.user_id,
- ROW_NUMBER() OVER (PARTITION BY po.partner_id ORDER BY po.create_date DESC) AS order_rank
- FROM purchase_order po
- RIGHT JOIN purchase_order_line pol ON pol.order_id = po.id
- LEFT JOIN res_partner rp ON rp.id = po.partner_id
- ),
- random_user_ids AS (
- SELECT DISTINCT
- CASE
- WHEN vendor_id = 5571 THEN 27
- WHEN vendor_id = 9688 THEN 397
- WHEN vendor_id = 35475 THEN 397
- WHEN vendor_id = 29712 THEN 397
- ELSE (CASE WHEN random() < 0.5 THEN 397 ELSE 1036 END)
- END AS user_id,
- vendor_id
- FROM (
- SELECT
- sol.vendor_id
- FROM v_sales_outstanding vso
- LEFT JOIN sale_order_line sol ON sol.id = vso.sale_line_id
- ) AS sub
- WHERE sub.vendor_id IS NOT NULL
- )
- SELECT
- pmp.product_id AS id,
- pmp.product_id,
- sub.vendor_id,
- pmp.brand,
- pmp.item_code,
- pmp.product,
- MAX(pmp.onhand) AS onhand,
- MAX(pmp.incoming) AS incoming,
- MAX(pmp.outgoing) AS outgoing,
- pmp.action,
- MAX(pjs.status_apo) AS status_apo,
- MAX(pjs.note) AS note,
- ru.user_id AS purchase_representative_id
- FROM v_procurement_monitoring_by_product pmp
+ CREATE OR REPLACE VIEW %s
+ AS SELECT pmp.product_id AS id,
+ pmp.product_id,
+ sub.vendor_id,
+ pmp.brand,
+ pmp.item_code,
+ pmp.product,
+ max(pmp.onhand) AS onhand,
+ max(pmp.incoming) AS incoming,
+ max(pmp.outgoing) AS outgoing,
+ pmp.action,
+ max(pjs.status_apo::text) AS status_apo,
+ max(pjs.note::text) AS note,
+ CASE
+ WHEN sub.vendor_id = 5571 THEN 27
+ WHEN sub.vendor_id = 9688 THEN 397
+ WHEN sub.vendor_id = 35475 THEN 397
+ WHEN sub.vendor_id = 29712 THEN 397
+ ELSE 1036
+ END AS purchase_representative_id
+ FROM v_procurement_monitoring_by_product pmp
LEFT JOIN purchasing_job_state pjs ON pjs.purchasing_job_id = pmp.product_id
- LEFT JOIN (
- SELECT
- vso.product_id,
- sol.vendor_id
+ LEFT JOIN (
+ SELECT vso.product_id,
+ max(sol.vendor_id) as vendor_id
FROM v_sales_outstanding vso
LEFT JOIN sale_order_line sol ON sol.id = vso.sale_line_id
- ) AS sub ON sub.product_id = pmp.product_id
- LEFT JOIN latest_purchase_orders po ON po.product_id = pmp.product_id
- LEFT JOIN random_user_ids ru ON ru.vendor_id = sub.vendor_id OR (ru.vendor_id IS NULL AND sub.vendor_id != 9688)
- WHERE pmp.action = 'kurang'
- AND sub.vendor_id IS NOT NULL
- GROUP BY
- pmp.product_id,
- pmp.brand,
- pmp.item_code,
- pmp.product,
- pmp.action,
- sub.vendor_id,
- ru.user_id
- )
+ group by vso.product_id
+ ) sub ON sub.product_id = pmp.product_id
+ WHERE pmp.action = 'kurang'::text AND sub.vendor_id IS NOT NULL
+ GROUP BY pmp.product_id, pmp.brand, pmp.item_code, pmp.product, pmp.action, sub.vendor_id;
""" % self._table)
-
def open_form_multi_generate_request_po(self):
action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_purchasing_job_multi_update')
action['context'] = {
diff --git a/indoteknik_custom/models/report_stock_forecasted.py b/indoteknik_custom/models/report_stock_forecasted.py
index 5f9427f8..ebdc7d4a 100644
--- a/indoteknik_custom/models/report_stock_forecasted.py
+++ b/indoteknik_custom/models/report_stock_forecasted.py
@@ -33,7 +33,6 @@ class ReplenishmentReport(models.AbstractModel):
'reserved_from': result,
'qty_fullfillment': quantity,
})
-
return lines
def _calculate_result(self, line):
diff --git a/indoteknik_custom/models/requisition.py b/indoteknik_custom/models/requisition.py
index 2b148c96..c4104ec5 100644
--- a/indoteknik_custom/models/requisition.py
+++ b/indoteknik_custom/models/requisition.py
@@ -214,6 +214,7 @@ class RequisitionLine(models.Model):
last_price = fields.Float(string='Last Price')
last_order_id = fields.Many2one('purchase.order', string='Last Order')
last_orderline_id = fields.Many2one('purchase.order.line', string='Last Order Line')
+ taxes_id = fields.Many2one('account.tax', string='Tax')
is_po = fields.Boolean(String='Is PO')
current_po_id = fields.Many2one('purchase.order', string='Current')
current_po_line_id = fields.Many2one('purchase.order.line', string='Current Line')
@@ -221,6 +222,23 @@ class RequisitionLine(models.Model):
qty_available_store = fields.Float(string='Available')
suggest = fields.Char(string='Suggest')
+ def _get_valid_purchase_price(self, purchase_price):
+ price = 0
+ taxes = ''
+ human_last_update = purchase_price.human_last_update or datetime.min
+ system_last_update = purchase_price.system_last_update or datetime.min
+
+ if purchase_price.taxes_product_id.type_tax_use == 'purchase':
+ price = purchase_price.product_price
+ taxes = purchase_price.taxes_product_id.id
+
+ if system_last_update > human_last_update:
+ if purchase_price.taxes_system_id.type_tax_use == 'purchase':
+ price = purchase_price.system_price
+ taxes = purchase_price.taxes_system_id.id
+
+ return price, taxes
+
@api.onchange('price_unit')
def _onchange_price_unit(self):
for line in self:
@@ -230,6 +248,15 @@ class RequisitionLine(models.Model):
def _onchange_product(self):
for line in self:
line.brand_id = line.product_id.product_tmpl_id.x_manufacture.id
+ purchase_pricelist = self.env['purchase.pricelist'].search([
+ ('product_id', '=', line.product_id.id)
+ ],order='count_trx_po desc, count_trx_po_vendor desc', limit=1)
+
+ price, taxes = line._get_valid_purchase_price(purchase_pricelist)
+ line.price_unit = price
+ line.taxes_id = taxes
+ line.partner_id = purchase_pricelist.vendor_id.id
+
class RequisitionPurchaseMatch(models.Model):
_name = 'requisition.purchase.match'
diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py
index 2846c14b..25db16d0 100644
--- a/indoteknik_custom/models/res_partner.py
+++ b/indoteknik_custom/models/res_partner.py
@@ -18,8 +18,8 @@ class ResPartner(models.Model):
('pkp', 'PKP'),
('nonpkp', 'Non PKP')
])
- sppkp = fields.Char(string="SPPKP", tracking=3)
- npwp = fields.Char(string="NPWP", tracking=3)
+ sppkp = fields.Char(string="SPPKP", tracking=True)
+ npwp = fields.Char(string="NPWP", tracking=True)
counter = fields.Integer(string="Counter", default=0)
leadtime = fields.Integer(string="Leadtime", default=0)
digital_invoice_tax = fields.Boolean(string="Digital Invoice & Faktur Pajak")
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index 8f48e898..1ad08154 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -78,6 +78,7 @@ class SaleOrder(models.Model):
payment_link_midtrans = fields.Char(string='Payment Link', help='Url payment yg digenerate oleh midtrans, harap diserahkan ke customer agar dapat dilakukan pembayaran secara mandiri')
payment_qr_code = fields.Binary("Payment QR Code")
due_id = fields.Many2one('due.extension', string="Due Extension", readonly=True, tracking=True)
+ vendor_approval_id = fields.Many2one('vendor.approval', string="Vendor Approval", readonly=True, tracking=True)
customer_type = fields.Selection([
('pkp', 'PKP'),
('nonpkp', 'Non PKP')
@@ -104,6 +105,7 @@ class SaleOrder(models.Model):
('cust_procurement', 'Customer Procurement')
], string='Web Approval', copy=False)
compute_fullfillment = fields.Boolean(string='Compute Fullfillment', compute="_compute_fullfillment")
+ vendor_approval = fields.Boolean(string='Vendor Approval')
note_ekspedisi = fields.Char(string="Note Ekspedisi")
date_kirim_ril = fields.Datetime(string='Tanggal Kirim SJ', compute='_compute_date_kirim', copy=False)
date_status_done = fields.Datetime(string='Date Done DO', compute='_compute_date_kirim', copy=False)
@@ -133,49 +135,112 @@ class SaleOrder(models.Model):
'account.payment.term', string='Payment Terms', check_company=True, # Unrequired company
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", tracking=True)
+ total_weight = fields.Float(string='Total Weight', compute='_compute_total_weight')
+ pareto_status = fields.Selection([
+ ('PR', 'Pareto Repeating'),
+ ('PPR', 'Potensi Pareto Repeating'),
+ ('PNR', 'Pareto Non Repeating'),
+ ('NP', 'Non Pareto')
+ ])
+
+ def _compute_total_weight(self):
+ total_weight = 0
+ missing_weight_products = []
+
+ for line in self.order_line:
+ if line.weight:
+ total_weight += line.weight * line.product_uom_qty
+
+ self.total_weight = total_weight
+
+ def action_indoteknik_estimate_shipping(self):
+ if not self.real_shipping_id.kota_id.is_jabodetabek:
+ raise UserError('Estimasi ongkir hanya bisa dilakukan di kota Jabodetabek')
+
+ total_weight = 0
+ missing_weight_products = []
+
+ for line in self.order_line:
+ if line.weight:
+ total_weight += line.weight * line.product_uom_qty
+ line.product_id.weight = line.weight
+ else:
+ missing_weight_products.append(line.product_id.name)
+
+ if missing_weight_products:
+ product_names = '<br/>'.join(missing_weight_products)
+ self.message_post(body=f"Produk berikut tidak memiliki berat:<br/>{product_names}")
+
+ if total_weight == 0:
+ raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.")
+
+ if total_weight < 10:
+ total_weight = 10
+ self.delivery_amt = total_weight * 3000
+
def action_estimate_shipping(self):
+ if self.carrier_id.id in [1, 151]:
+ self.action_indoteknik_estimate_shipping()
+ return
total_weight = 0
missing_weight_products = []
for line in self.order_line:
if line.weight:
- total_weight += line.weight
+ total_weight += line.weight * line.product_uom_qty
+ line.product_id.weight = line.weight
else:
missing_weight_products.append(line.product_id.name)
+ if missing_weight_products:
+ product_names = '<br/>'.join(missing_weight_products)
+ self.message_post(body=f"Produk berikut tidak memiliki berat:<br/>{product_names}")
+
if total_weight == 0:
raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.")
# Mendapatkan city_id berdasarkan nama kota
origin_city_name = self.warehouse_id.partner_id.kota_id.name
- destination_city_name = self.real_shipping_id.kota_id.name
-
- origin_city_id = self._get_city_id_by_name(origin_city_name)
- destination_city_id = self._get_city_id_by_name(destination_city_name)
+ destination_subsdistrict_id = self.real_shipping_id.kecamatan_id.rajaongkir_id
- if not origin_city_id or not destination_city_id:
+ if not destination_subsdistrict_id:
raise UserError("Gagal mendapatkan ID kota asal atau tujuan.")
- result = self._call_rajaongkir_api(total_weight, origin_city_id, destination_city_id)
+ result = self._call_rajaongkir_api(total_weight, destination_subsdistrict_id)
if result:
estimated_cost = result['rajaongkir']['results'][0]['costs'][0]['cost'][0]['value']
self.delivery_amt = estimated_cost
- self.message_post(body=f"Estimasi Ongkos Kirim: {estimated_cost}")
+
+ shipping_info = []
+ for courier in result['rajaongkir']['results']:
+ for cost_detail in courier['costs']:
+ service = cost_detail['service']
+ description = cost_detail['description']
+ etd = cost_detail['cost'][0]['etd']
+ value = cost_detail['cost'][0]['value']
+ shipping_info.append(f"Service: {service}, Description: {description}, ETD: {etd} hari, Cost: Rp {value}")
+
+ log_message = "<br/>".join(shipping_info)
+
+ description_ongkir = result['rajaongkir']['results'][0]['costs'][0]['description']
+ etd_ongkir = result['rajaongkir']['results'][0]['costs'][0]['cost'][0]['etd']
+ service_ongkir = result['rajaongkir']['results'][0]['costs'][0]['service']
+ self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}<br/>Service: {service_ongkir}<br/>Description: {description_ongkir}<br/>ETD: {etd_ongkir}<br/>Detail Lain:<br/>{log_message}")
else:
raise UserError("Gagal mendapatkan estimasi ongkir.")
- def _call_rajaongkir_api(self, total_weight, origin_city_id, destination_city_id):
+ def _call_rajaongkir_api(self, total_weight, destination_subsdistrict_id):
url = 'https://pro.rajaongkir.com/api/cost'
headers = {
- 'key': '7ac9883688da043b50cc32f0e3070bb6',
+ 'key': '9b1310f644056d84d60b0af6bb21611a',
}
courier = self.carrier_id.name.lower()
data = {
- 'origin': int(origin_city_id),
- 'originType': 'city',
- 'destination': int(destination_city_id),
- 'destinationType': 'city',
+ 'origin': 2127,
+ 'originType': 'subdistrict',
+ 'destination': int(destination_subsdistrict_id),
+ 'destinationType': 'subdistrict',
'weight': int(total_weight * 1000),
'courier': courier,
}
@@ -186,16 +251,13 @@ class SaleOrder(models.Model):
return None
def _normalize_city_name(self, city_name):
- # Ubah nama kota menjadi huruf kecil
city_name = city_name.lower()
- # Hilangkan prefiks "kabupaten" atau "kota" jika ada
if city_name.startswith('kabupaten'):
city_name = city_name.replace('kabupaten', '').strip()
elif city_name.startswith('kota'):
city_name = city_name.replace('kota', '').strip()
- # Hilangkan spasi yang berlebihan
city_name = " ".join(city_name.split())
return city_name
@@ -203,10 +265,9 @@ class SaleOrder(models.Model):
def _get_city_id_by_name(self, city_name):
url = 'https://pro.rajaongkir.com/api/city'
headers = {
- 'key': '7ac9883688da043b50cc32f0e3070bb6',
+ 'key': '9b1310f644056d84d60b0af6bb21611a',
}
- # Normalisasi nama kota sebelum melakukan pencarian
normalized_city_name = self._normalize_city_name(city_name)
response = requests.get(url, headers=headers)
@@ -220,14 +281,17 @@ class SaleOrder(models.Model):
def _get_subdistrict_id_by_name(self, city_id, subdistrict_name):
url = f'https://pro.rajaongkir.com/api/subdistrict?city={city_id}'
headers = {
- 'key': '7ac9883688da043b50cc32f0e3070bb6',
+ 'key': '9b1310f644056d84d60b0af6bb21611a',
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
subdistrict_data = response.json()
for subdistrict in subdistrict_data['rajaongkir']['results']:
- if subdistrict['subdistrict_name'].lower() == subdistrict_name.lower():
+ subsdistrict_1 = subdistrict['subdistrict_name'].lower()
+ subsdistrict_2 = subdistrict_name.lower()
+
+ if subsdistrict_1 == subsdistrict_2:
return subdistrict['subdistrict_id']
return None
@@ -412,6 +476,22 @@ class SaleOrder(models.Model):
for line in order.order_line:
total += line.vendor_subtotal
order.purchase_total = total
+
+ def check_data_real_delivery_address(self):
+ real_delivery_address = self.real_shipping_id
+
+ if not real_delivery_address.state_id:
+ raise UserError('State Real Delivery Address harus diisi')
+ if not real_delivery_address.zip:
+ raise UserError('Zip code Real Delivery Address harus diisi')
+ if not real_delivery_address.mobile:
+ raise UserError('Mobile Real Delivery Address harus diisi')
+ if not real_delivery_address.phone:
+ raise UserError('Phone Real Delivery Address harus diisi')
+ if not real_delivery_address.kecamatan_id:
+ raise UserError('Kecamatan Real Delivery Address harus diisi')
+ if not real_delivery_address.kelurahan_id:
+ raise UserError('Kelurahan Real Delivery Address harus diisi')
def generate_payment_link_midtrans_sales_order(self):
# midtrans_url = 'https://app.sandbox.midtrans.com/snap/v1/transactions' # dev - sandbox
@@ -558,8 +638,6 @@ class SaleOrder(models.Model):
raise UserError('Phone Real Delivery Address harus diisi')
if not real_delivery_address.kecamatan_id:
raise UserError('Kecamatan Real Delivery Address harus diisi')
- if not real_delivery_address.kelurahan_id:
- raise UserError('Kelurahan Real Delivery Address harus diisi')
@api.onchange('partner_id')
def onchange_partner_contact(self):
@@ -570,6 +648,7 @@ class SaleOrder(models.Model):
self.sppkp = parent_id.sppkp
self.customer_type = parent_id.customer_type
self.email = parent_id.email
+ self.pareto_status = parent_id.pareto_status
@api.onchange('partner_id')
def onchange_partner_id(self):
@@ -661,10 +740,14 @@ class SaleOrder(models.Model):
for order in self:
order.order_line.validate_line()
+ term_days = 0
+ for term_line in order.payment_term_id.line_ids:
+ term_days += term_line.days
+
partner = order.partner_id.parent_id or order.partner_id
if not partner.property_payment_term_id:
raise UserError("Payment Term pada Master Data Customer harus diisi")
- if not partner.active_limit:
+ if not partner.active_limit and term_days > 0:
raise UserError("Credit Limit pada Master Data Customer harus diisi")
if order.payment_term_id != partner.property_payment_term_id:
raise UserError("Payment Term berbeda pada Master Data Customer")
@@ -678,16 +761,23 @@ class SaleOrder(models.Model):
raise UserError("Salesperson sudah tidak aktif, mohon diisi yang benar pada data SO dan Contact")
def sale_order_approve(self):
+ if self.validate_different_vendor() and not self.vendor_approval and not self.vendor_approval_id:
+ return self._create_notification_action('Notification', 'Terdapat Vendor yang berbeda dengan MD Vendor')
self.check_due()
self._validate_order()
for order in self:
order.order_line.validate_line()
+
+ term_days = 0
+ for term_line in order.payment_term_id.line_ids:
+ term_days += term_line.days
+
partner = order.partner_id.parent_id or order.partner_id
if not partner.property_payment_term_id:
raise UserError("Payment Term pada Master Data Customer harus diisi")
- if not partner.active_limit:
+ if not partner.active_limit and term_days > 0:
raise UserError("Credit Limit pada Master Data Customer harus diisi")
if order.payment_term_id != partner.property_payment_term_id:
raise UserError("Payment Term berbeda pada Master Data Customer")
@@ -781,9 +871,56 @@ class SaleOrder(models.Model):
'body_html': email_body,
'email_to': salesperson_email,
}).send()
+
+ def validate_different_vendor(self):
+ if self.vendor_approval_id and self.vendor_approval_id.state == 'draft':
+ raise UserError('SO ini sedang dalam review Vendor Approval')
+
+ if self.vendor_approval_id and self.vendor_approval_id.state == 'cancel':
+ raise UserError('Vendor Approval SO ini Di Reject')
+
+ if self.vendor_approval_id and self.vendor_approval_id.state == 'done':
+ return False
+
+ different_vendor = self.order_line.filtered(lambda l: l.vendor_id and l.vendor_md_id and l.vendor_id.id != l.vendor_md_id.id)
+ if different_vendor:
+ vendor_approval = self.env['vendor.approval'].create({
+ 'order_id': self.id,
+ 'create_date_so': self.create_date,
+ 'partner_id': self.partner_id.id,
+ 'state': 'draft',
+ })
+
+ self.vendor_approval_id = vendor_approval.id
+
+ for line in different_vendor:
+ self.env['vendor.approval.line'].create({
+ 'vendor_approval_id': vendor_approval.id,
+ 'product_id': line.product_id.id,
+ 'product_uom_qty': line.product_uom_qty,
+ 'vendor_id': line.vendor_id.id,
+ 'vendor_md_id': line.vendor_md_id.id,
+ 'purchase_price': line.purchase_price,
+ 'purchase_price_md': line.purchase_price_md,
+ 'sales_price': line.price_unit,
+ 'margin_before': line.margin_md,
+ 'margin_after': line.item_percent_margin,
+ 'purchase_tax_id': line.purchase_tax_id.id,
+ 'sales_tax_id': line.tax_id[0].id if line.tax_id else False,
+ 'percent_margin_difference': (line.price_unit - line.purchase_price_md) / line.purchase_price_md if line.purchase_price_md else False,
+ })
+
+ return True
+ else:
+ return False
+
def action_confirm(self):
for order in self:
+ if self.validate_different_vendor() and not self.vendor_approval and not self.vendor_approval_id:
+ return self._create_notification_action('Notification', 'Terdapat Vendor yang berbeda dengan MD Vendor')
+
+ order.check_data_real_delivery_address()
order.sale_order_check_approve()
order._validate_order()
order.order_line.validate_line()
@@ -896,11 +1033,20 @@ class SaleOrder(models.Model):
def _set_sppkp_npwp_contact(self):
partner = self.partner_id.parent_id or self.partner_id
- if not partner.sppkp or not partner.npwp or not partner.email or partner.customer_type:
- partner.customer_type = self.customer_type
- partner.npwp = self.npwp
+ if not partner.sppkp:
partner.sppkp = self.sppkp
+ if not partner.npwp:
+ partner.npwp = self.npwp
+ if not partner.email:
partner.email = self.email
+ if not partner.customer_type:
+ partner.customer_type = self.customer_type
+
+ # if not partner.sppkp or not partner.npwp or not partner.email or partner.customer_type:
+ # partner.customer_type = self.customer_type
+ # partner.npwp = self.npwp
+ # partner.sppkp = self.sppkp
+ # partner.email = self.email
def _compute_total_margin(self):
for order in self:
@@ -1170,7 +1316,7 @@ class SaleOrder(models.Model):
def create(self, vals):
# Ensure partner details are updated when a sale order is created
order = super(SaleOrder, self).create(vals)
- order._update_partner_details()
+ # order._update_partner_details()
return order
def write(self, vals):
@@ -1178,8 +1324,8 @@ class SaleOrder(models.Model):
res = super(SaleOrder, self).write(vals)
# Check if the update is coming from a save operation
- if any(field in vals for field in ['sppkp', 'npwp', 'email', 'customer_type']):
- self._update_partner_details()
+ # if any(field in vals for field in ['sppkp', 'npwp', 'email', 'customer_type']):
+ # self._update_partner_details()
return res
diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py
index d1dcd0af..5e01067a 100644
--- a/indoteknik_custom/models/sale_order_line.py
+++ b/indoteknik_custom/models/sale_order_line.py
@@ -14,7 +14,9 @@ class SaleOrderLine(models.Model):
states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]"
)
+ vendor_md_id = fields.Many2one('res.partner', string='MD Vendor')
purchase_price = fields.Float('Purchase', required=True, digits='Product Price', default=0.0)
+ purchase_price_md = fields.Float('MD Purchase')
purchase_tax_id = fields.Many2one('account.tax', string='Tax', domain=['|', ('active', '=', False), ('active', '=', True)])
delivery_amt_line = fields.Float('DeliveryAmtLine', compute='compute_delivery_amt_line')
fee_third_party_line = fields.Float('FeeThirdPartyLine', compute='compute_fee_third_party_line', default=0)
@@ -32,6 +34,8 @@ class SaleOrderLine(models.Model):
reserved_from = fields.Char(string='Reserved From', copy=False)
item_percent_margin_without_deduction = fields.Float('%Margin', compute='_compute_item_margin_without_deduction')
weight = fields.Float(string='Weight')
+ md_vendor_id = fields.Many2one('res.partner', string='MD Vendor', readonly=True)
+ margin_md = fields.Float(string='Margin MD')
@api.constrains('note_procurement')
def note_procurement_to_apo(self):
@@ -125,6 +129,9 @@ class SaleOrderLine(models.Model):
else:
line.item_percent_margin = 0
+ if not line.margin_md:
+ line.margin_md = line.item_percent_margin
+
@api.onchange('vendor_id')
def onchange_vendor_id(self):
# TODO : need to change this logic @stephan
@@ -244,11 +251,14 @@ class SaleOrderLine(models.Model):
# purchase_price = self.env['purchase.pricelist'].search(
# query, limit=1, order='count_trx_po desc, count_trx_po_vendor desc')
price, taxes, vendor_id = self._get_purchase_price(line.product_id)
+ line.vendor_md_id = vendor_id
line.vendor_id = vendor_id
+ line.margin_md = line.item_percent_margin
line.tax_id = line.order_id.sales_tax_id
# price, taxes = line._get_valid_purchase_price(purchase_price)
+ line.purchase_price_md = price
line.purchase_price = price
- line.purchase_tax_id = taxes
+ line.purchase_tax_id = taxes
attribute_values = line.product_id.product_template_attribute_value_ids.mapped('name')
attribute_values_str = ', '.join(attribute_values) if attribute_values else ''
diff --git a/indoteknik_custom/models/solr/product_product.py b/indoteknik_custom/models/solr/product_product.py
index 35e3a4b3..7c10a910 100644
--- a/indoteknik_custom/models/solr/product_product.py
+++ b/indoteknik_custom/models/solr/product_product.py
@@ -47,6 +47,15 @@ class ProductProduct(models.Model):
category_id = category.id
category_name = category.name
+ # Check if the product's inventory location is in ID 57 or 83
+ target_locations = [57, 83]
+ stock_quant = self.env['stock.quant'].search([
+ ('product_id', 'in', variant.product_variant_ids.ids),
+ ('location_id', 'in', target_locations),
+ ])
+
+ is_in_bu = any(quant.available_quantity > 0 for quant in stock_quant)
+
document = solr_model.get_doc('variants', variant.id)
document.update({
@@ -76,7 +85,8 @@ class ProductProduct(models.Model):
'publish_b': not variant.unpublished,
'sni_b': variant.sni,
'tkdn_b': variant.tkdn,
- 'qty_sold_f': variant.qty_sold
+ 'qty_sold_f': variant.qty_sold,
+ "is_in_bu_b": is_in_bu,
})
self.solr().add(docs=[document], softCommit=True)
diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py
index 1eb6f31b..1d54cc9b 100644
--- a/indoteknik_custom/models/solr/product_template.py
+++ b/indoteknik_custom/models/solr/product_template.py
@@ -74,10 +74,10 @@ class ProductTemplate(models.Model):
target_locations = [57, 83]
stock_quant = self.env['stock.quant'].search([
('product_id', 'in', template.product_variant_ids.ids),
- ('location_id', 'in', target_locations)
+ ('location_id', 'in', target_locations),
])
- is_in_bu = bool(stock_quant)
+ is_in_bu = any(quant.available_quantity > 0 for quant in stock_quant)
cleaned_desc = BeautifulSoup(template.website_description or '', "html.parser").get_text()
website_description = template.website_description if cleaned_desc else ''
@@ -158,10 +158,14 @@ class ProductTemplate(models.Model):
traverse_category(category.parent_id.id)
# Start traversal from the initial category
- traverse_category(category_id)
-
- # Reverse the list to get the hierarchy from top level to the current level
- return list(reversed(category_ids))
+ if category_id: # Check if category_id is not an empty value
+ traverse_category(category_id)
+
+ # Reverse the list to get the hierarchy from top level to the current level
+ return list(reversed(category_ids))
+ else:
+ # If category_id is empty, return an empty list
+ return []
def _sync_price_to_solr(self):
solr_model = self.env['apache.solr']
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index 6f038386..2a73d631 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -3,7 +3,7 @@ from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.tools.float_utils import float_is_zero
from datetime import datetime
from itertools import groupby
-import pytz, datetime
+import pytz, datetime, requests, json
class StockPicking(models.Model):
@@ -73,10 +73,8 @@ class StockPicking(models.Model):
('hold', 'Hold by Sales'),
('not_paid', 'Customer belum bayar'),
('partial', 'Kirim Parsial'),
- ('not_complete', 'Belum Lengkap'),
('indent', 'Indent'),
('self_pickup', 'Barang belum di pickup Customer'),
- ('delivery_route', 'Belum masuk rute pengiriman'),
('expedition_closed', 'Eskpedisi belum buka')
], string='Note Logistic', help='jika field ini diisi maka tidak akan dihitung ke lead time')
waybill_id = fields.One2many(comodel_name='airway.bill', inverse_name='do_id', string='Airway Bill')
@@ -94,6 +92,8 @@ class StockPicking(models.Model):
date_availability = fields.Datetime(string="Date Availability", copy=False, tracking=True)
sale_order = fields.Char(string='Matches SO', copy=False)
printed_sj = fields.Boolean('Printed Surat Jalan', help='flag which is internal use or not')
+ printed_sj_retur = fields.Boolean('Printed Surat Jalan Retur', help='flag which is internal use or not')
+ date_printed_sj_retur = fields.Datetime(string='Status Printed Surat Jalan Retur', copy=False, tracking=True)
invoice_status = fields.Selection([
('upselling', 'Upselling Opportunity'),
('invoiced', 'Fully Invoiced'),
@@ -101,6 +101,58 @@ class StockPicking(models.Model):
('no', 'Nothing to Invoice')
], string='Invoice Status', related="sale_id.invoice_status")
+ state_reserve = fields.Selection([
+ ('waiting', 'Waiting For Fullfilment'),
+ ('ready', 'Ready to Ship'),
+ ('done', 'Done'),
+ ('cancel', 'Cancelled'),
+ ], string='Status Reserve', readonly=True, tracking=True, help="The current state of the stock picking.")
+
+ def action_send_to_biteship(self):
+ url = "https://api.biteship.com/v1/orders"
+
+ api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA"
+
+ items_data = []
+ for item in self.items:
+ items_data.append({
+ "name": item.name,
+ "description": item.description,
+ "category": item.category,
+ "value": item.value,
+ "quantity": item.quantity,
+ "weight": item.weight
+ })
+
+ payload = {
+ "shipper_contact_name": self.carrier_id.pic_name or '',
+ "shipper_contact_phone": self.carrier_id.pic_phone or '',
+ # "shipper_contact_email": "sales@indoteknik.co.id",
+ "shipper_organization": self.carrier_id.name,
+ "origin_contact_name": "PT. Indoteknik Dotcom Gemilang",
+ "origin_contact_phone": "081717181922",
+ "origin_address": "Jl. Bandengan Utara Komp A & BRT. Penjaringan, Kec. Penjaringan, Jakarta (BELAKANG INDOMARET) KOTA JAKARTA UTARA PENJARINGAN",
+ "origin_postal_code": "14440",
+ "destination_contact_name": self.real_shipping_id.name,
+ "destination_contact_phone": self.real_shipping_id.phone or self.real_shipping_id.mobile,
+ "destination_contact_email": self.real_shipping_id.email or '',
+ "destination_address": self.real_shipping_id.street,
+ "destination_postal_code": self.real_shipping_id.zip,
+ "items": items_data
+ }
+
+ headers = {
+ "Authorization": f"Bearer {api_key}",
+ "Content-Type": "application/json"
+ }
+
+ response = requests.post(url, headers=headers, data=json.dumps(payload))
+
+ if response.status_code == 201:
+ return response.json()
+ else:
+ raise UserError(f"Error saat mengirim ke Biteship: {response.content}")
+
@api.constrains('driver_departure_date')
def constrains_driver_departure_date(self):
self.date_doc_kirim = self.driver_departure_date
@@ -125,16 +177,54 @@ class StockPicking(models.Model):
raise UserError('Hanya Logistic yang bisa mengubah shipping method')
def do_unreserve(self):
- if not self._context.get('darimana') == 'sale.order':
+ group_id = self.env.ref('indoteknik_custom.group_role_it').id
+ users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])])
+ if not self._context.get('darimana') == 'sale.order' and self.env.user.id not in users_in_group.mapped('id'):
self.sale_id.unreserve_id = self.id
return self._create_approval_notification('Logistic')
res = super(StockPicking, self).do_unreserve()
current_time = datetime.datetime.utcnow()
self.date_unreserve = current_time
+ # self.check_state_reserve()
return res
+ # def check_state_reserve(self):
+ # do = self.search([
+ # ('state', 'not in', ['cancel', 'draft', 'done']),
+ # ('picking_type_code', '=', 'outgoing')
+ # ])
+
+ # for rec in do:
+ # rec.state_reserve = 'ready'
+ # rec.date_reserved = datetime.datetime.utcnow()
+
+ # for line in rec.move_ids_without_package:
+ # if line.product_uom_qty > line.reserved_availability:
+ # rec.state_reserve = 'waiting'
+ # rec.date_reserved = ''
+ # break
+
+ def check_state_reserve(self):
+ picking = self.search([
+ ('state', 'not in', ['cancel', 'draft', 'done']),
+ ('picking_type_code', '=', 'outgoing')
+ ])
+
+ for data in picking:
+ fullfilment = self.env['sales.order.fullfillment'].search([
+ ('sales_order_id', '=', data.sale_id.id)
+ ])
+
+ data.state_reserve = 'ready'
+ data.date_reserved = datetime.datetime.utcnow()
+ for rec in fullfilment:
+ if rec.reserved_from not in ['Inventory On Hand', 'Reserved from stock', 'Free Stock']:
+ data.state_reserve = 'waiting'
+ data.date_reserved = ''
+ break
+
def _create_approval_notification(self, approval_role):
title = 'Warning'
message = f'Butuh approval sales untuk unreserved'
@@ -273,6 +363,7 @@ class StockPicking(models.Model):
current_time = datetime.datetime.utcnow()
self.real_shipping_id = self.sale_id.real_shipping_id
self.date_availability = current_time
+ # self.check_state_reserve()
return res
def ask_approval(self):
@@ -419,6 +510,7 @@ class StockPicking(models.Model):
res = super(StockPicking, self).button_validate()
self.calculate_line_no()
self.date_done = datetime.datetime.utcnow()
+ self.state_reserve = 'done'
return res
@api.model
diff --git a/indoteknik_custom/models/user_company_request.py b/indoteknik_custom/models/user_company_request.py
index f86f3872..86e66934 100644
--- a/indoteknik_custom/models/user_company_request.py
+++ b/indoteknik_custom/models/user_company_request.py
@@ -13,6 +13,39 @@ class UserCompanyRequest(models.Model):
('approved', 'Approve'),
('rejected', 'Reject'),
], string='Approval')
+ similar_company_ids = fields.Many2many('res.partner', compute="_compute_similar_companies", string="Similar Companies")
+
+ @api.depends('user_input')
+ def _compute_similar_companies(self):
+ for record in self:
+ if record.user_input:
+ record.similar_company_ids = [(6, 0, self.get_similar_companies(record.user_input))]
+ else:
+ record.similar_company_ids = [(6, 0, [])]
+
+ # def get_similar_companies(self, user_input):
+ # query = """
+ # SELECT id
+ # FROM res_partner
+ # WHERE levenshtein(name::text, %s) < 3
+ # ORDER BY levenshtein(name::text, %s) ASC
+ # """
+ # self.env.cr.execute(query, (user_input, user_input))
+ # return [row[0] for row in self.env.cr.fetchall()]
+
+ def get_similar_companies(self, user_input):
+ query = """
+ SELECT id
+ FROM res_partner
+ WHERE (name ILIKE %s OR levenshtein(name::text, %s) < 3)
+ AND active = TRUE AND is_company = TRUE
+ ORDER BY levenshtein(name::text, %s) ASC
+ """
+ # Menggunakan '%' untuk mencocokkan nama perusahaan sebagian
+ self.env.cr.execute(query, ('%' + user_input + '%', user_input, user_input))
+ company_ids = [row[0] for row in self.env.cr.fetchall()]
+ return company_ids
+
internal_input = fields.Char(string='Internal Input')
company_type = fields.Char(string='Company Type', compute='_compute_company_type')
diff --git a/indoteknik_custom/models/vendor_approval.py b/indoteknik_custom/models/vendor_approval.py
new file mode 100644
index 00000000..b0d58b85
--- /dev/null
+++ b/indoteknik_custom/models/vendor_approval.py
@@ -0,0 +1,73 @@
+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 VendorApproval(models.Model):
+ _name = "vendor.approval"
+ _description = "Vendor Approval"
+ _inherit = ['mail.thread']
+ _rec_name = 'number'
+
+ number = fields.Char(string='Document No', index=True, copy=False, readonly=True, tracking=True)
+ partner_id = fields.Many2one('res.partner', string="Customer", readonly=True)
+ order_id = fields.Many2one('sale.order', string="SO", readonly=True)
+ vendor_approval_line = fields.One2many('vendor.approval.line', 'vendor_approval_id', string='Vendor Approval Lines', auto_join=True)
+ state = fields.Selection([('draft', 'Draft'), ('done', 'Done'), ('cancel', 'Reject')], string='State', tracking=True)
+ create_date_so = fields.Datetime(string='Create Date SO', readonly=True)
+
+ @api.model
+ def create(self, vals):
+ vals['number'] = self.env['ir.sequence'].next_by_code('vendor.approval') or '0'
+ result = super(VendorApproval, self).create(vals)
+ return result
+
+ def action_approve(self):
+ if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'):
+ raise UserError('Hanya Merchandiser yang bisa approve')
+
+ self.state = 'done'
+ self.order_id.vendor_approval = True
+ self.order_id.action_confirm()
+ message = "Vendor Approval approved by %s" % (self.env.user.name)
+ self.order_id.message_post(body=message)
+
+
+ def action_reject(self):
+ if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'):
+ raise UserError('Hanya Merchandiser yang bisa cancel')
+
+ self.state = 'cancel'
+
+ message = "Vendor Approval rejected by %s" % (self.env.user.name)
+ self.order_id.message_post(body=message)
+
+ def unlink(self):
+ res = super(VendorApproval, self).unlink()
+ if not self._name == 'vendor.approval':
+ raise UserError('Vendor Approval tidak bisa didelete')
+ return res
+
+
+class VendorApprovalLine(models.Model):
+ _name = 'vendor.approval.line'
+ _description = 'Vendor Approval Line'
+ _order = 'vendor_approval_id, id'
+
+ vendor_approval_id = fields.Many2one('vendor.approval', string='Vendor Approval Ref', required=True, ondelete='cascade', index=True, copy=False)
+ product_id = fields.Many2one('product.product', string='Product')
+ product_uom_qty = fields.Float(string='Quantity')
+ vendor_id = fields.Many2one('res.partner', string='Vendor')
+ vendor_md_id = fields.Many2one('res.partner', string='Vendor MD')
+ sales_price = fields.Float(string='Sales Price')
+ margin_before = fields.Float(string='Margin Before')
+ margin_after = fields.Float(string='Margin After')
+ purchase_price = fields.Float(string='Purchase Price')
+ purchase_price_md= fields.Float(string='Purchase Price MD')
+ purchase_tax_id = fields.Many2one('account.tax', string='Purchase Tax', domain=['|', ('active', '=', False), ('active', '=', True)])
+ sales_tax_id = fields.Many2one('account.tax', string='Sales Tax', domain=['|', ('active', '=', False), ('active', '=', True)])
+ percent_margin_difference = fields.Float(string='Percent Margin Difference')
+
+
diff --git a/indoteknik_custom/models/website_user_cart.py b/indoteknik_custom/models/website_user_cart.py
index 169f4a6b..26e217cb 100644
--- a/indoteknik_custom/models/website_user_cart.py
+++ b/indoteknik_custom/models/website_user_cart.py
@@ -173,13 +173,12 @@ class WebsiteUserCart(models.Model):
}
return result
- def action_mail_reminder_to_checkout(self, limit=10):
- user_ids = self.search([]).mapped('user_id')[:limit]
+ def action_mail_reminder_to_checkout(self, limit=200):
+ user_ids = self.search([('is_reminder', '=', False)]).mapped('user_id')[:limit]
for user in user_ids:
- latest_cart = self.search([('user_id', '=', user.id), ('is_reminder', '=', False)], order='create_date desc', limit=1)
+ latest_cart = self.search([('user_id', '=', user.id)], order='create_date desc', limit=1)
- # Proses semua keranjang untuk user tersebut
carts_to_remind = self.search([('user_id', '=', user.id)])
if latest_cart and not latest_cart.is_reminder:
@@ -191,11 +190,9 @@ class WebsiteUserCart(models.Model):
cart.is_selected = False
cart.is_reminder = True
- # Mengirim email pengingat untuk keranjang terbaru
template = self.env.ref('indoteknik_custom.mail_template_user_cart_reminder_to_checkout')
template.send_mail(latest_cart.id, force_send=True)
-
def calculate_discount(self, user_id):
carts = self.search([('user_id', '=', user_id)])
voucher = self.env['voucher'].browse(146)
diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv
index 19e3bdca..553047e6 100755
--- a/indoteknik_custom/security/ir.model.access.csv
+++ b/indoteknik_custom/security/ir.model.access.csv
@@ -138,3 +138,8 @@ access_approval_date_doc,access.approval.date.doc,model_approval_date_doc,,1,1,1
access_account_tax,access.account.tax,model_account_tax,,1,1,1,1
access_approval_unreserve,access.approval.unreserve,model_approval_unreserve,,1,1,1,1
access_approval_unreserve_line,access.approval.unreserve.line,model_approval_unreserve_line,,1,1,1,1
+access_vendor_approval,access.vendor.approval,model_vendor_approval,,1,1,1,1
+access_vendor_approval_line,access.vendor.approval.line,model_vendor_approval_line,,1,1,1,1
+access_vit_kota,access.vit.kota,model_vit_kota,,1,1,1,1
+access_v_brand_product_category,access.v.brand.product.category,model_v_brand_product_category,,1,1,1,1
+access_web_find_page,access.web.find.page,model_web_find_page,,1,1,1,1
diff --git a/indoteknik_custom/views/account_move_views.xml b/indoteknik_custom/views/account_move_views.xml
index 4acafb14..da25636e 100644
--- a/indoteknik_custom/views/account_move_views.xml
+++ b/indoteknik_custom/views/account_move_views.xml
@@ -12,6 +12,10 @@
<field name="description"/>
<field name="approval_status"/>
<field name="is_approve"/>
+ <field name="approve_by" optional="hide"/>
+ <field name="date_approve" optional="hide"/>
+ <field name="create_uid" optional="hide"/>
+ <field name="create_date" optional="hide"/>
</tree>
</field>
</record>
@@ -58,12 +62,14 @@
<group>
<field name="partner_id" readonly="1"/>
<field name="day_extension" attrs="{'readonly': [('is_approve', '=', True)]}"/>
+ <field name="order_id" readonly="1"/>
</group>
<group>
<field name="is_approve" readonly="1"/>
- <field name="order_id" readonly="1"/>
<field name="counter" readonly="1"/>
<field name="approval_status" readonly="1"/>
+ <field name="approve_by" readonly="1"/>
+ <field name="date_approve" readonly="1"/>
</group>
</group>
<group>
diff --git a/indoteknik_custom/views/dunning_run.xml b/indoteknik_custom/views/dunning_run.xml
index 522be8c9..2117a7bb 100644
--- a/indoteknik_custom/views/dunning_run.xml
+++ b/indoteknik_custom/views/dunning_run.xml
@@ -14,6 +14,7 @@
<field name="date_terima_tukar_faktur"/>
<field name="shipper_faktur_id"/>
<field name="grand_total"/>
+ <field name="create_uid" optional="hide"/>
</tree>
</field>
</record>
diff --git a/indoteknik_custom/views/find_page.xml b/indoteknik_custom/views/find_page.xml
new file mode 100644
index 00000000..c752aa98
--- /dev/null
+++ b/indoteknik_custom/views/find_page.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<odoo>
+ <record id="web_find_page_tree" model="ir.ui.view">
+ <field name="name">web.find.page.tree</field>
+ <field name="model">web.find.page</field>
+ <field name="arch" type="xml">
+ <tree>
+ <field name="category_id"/>
+ <field name="brand_id"/>
+ <field name="url"/>
+ <field name="create_uid"/>
+ <field name="write_uid"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="web_find_page_form" model="ir.ui.view">
+ <field name="name">web.find.page.form</field>
+ <field name="model">web.find.page</field>
+ <field name="arch" type="xml">
+ <form>
+ <sheet string="Web Find Page">
+ <div class="oe_button_box" name="button_box"/>
+ <group>
+ <group>
+ <field name="category_id"/>
+ <field name="brand_id"/>
+ <field name="url"/>
+ </group>
+ <group>
+ <field name="create_uid"/>
+ <field name="write_uid"/>
+ </group>
+ </group>
+ </sheet>
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
+ </form>
+ </field>
+ </record>
+
+ <record id="view_web_find_page_filter" model="ir.ui.view">
+ <field name="name">web.find.page.list.select</field>
+ <field name="model">web.find.page</field>
+ <field name="priority" eval="15"/>
+ <field name="arch" type="xml">
+ <search string="Search Web Find Page">
+ <field name="category_id"/>
+ <field name="brand_id"/>
+ <field name="url"/>
+ </search>
+ </field>
+ </record>
+
+ <record id="web_find_page_action" model="ir.actions.act_window">
+ <field name="name">Web Find Page</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">web.find.page</field>
+ <field name="search_view_id" ref="view_web_find_page_filter"/>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+ <menuitem id="menu_web_find_page"
+ name="Web Find Page"
+ action="web_find_page_action"
+ parent="website_sale.menu_orders"
+ sequence="8"/>
+</odoo> \ No newline at end of file
diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml
index dd501d8c..dfb56100 100644
--- a/indoteknik_custom/views/ir_sequence.xml
+++ b/indoteknik_custom/views/ir_sequence.xml
@@ -21,6 +21,16 @@
<field name="number_increment">1</field>
</record>
+ <record id="sequence_vendor_approval" model="ir.sequence">
+ <field name="name">Vendor Approval</field>
+ <field name="code">vendor.approval</field>
+ <field name="active">TRUE</field>
+ <field name="prefix">VA/%(year)s/</field>
+ <field name="padding">5</field>
+ <field name="number_next">1</field>
+ <field name="number_increment">1</field>
+ </record>
+
<record id="sequence_approval_unreserve" model="ir.sequence">
<field name="name">Approval Unreserve</field>
<field name="code">approval.unreserve</field>
diff --git a/indoteknik_custom/views/product_template.xml b/indoteknik_custom/views/product_template.xml
index a77b99de..5a509ebd 100755
--- a/indoteknik_custom/views/product_template.xml
+++ b/indoteknik_custom/views/product_template.xml
@@ -39,12 +39,23 @@
</group>
</page>
</page>
- <field name="supplier_taxes_id" position="after">
+ <field name="supplier_taxes_id" position="after">
<field name="supplier_url" widget="url"/>
</field>
</field>
</record>
-
+
+ <record id="product_normal_form_view_inherit" model="ir.ui.view">
+ <field name="name">Product Template</field>
+ <field name="model">product.product</field>
+ <field name="inherit_id" ref="product.product_normal_form_view"/>
+ <field name="arch" type="xml">
+ <field name="last_update_solr" position="after">
+ <field name="clean_website_description" />
+ </field>
+ </field>
+ </record>
+
<record id="x_manufactures_group_by" model="ir.ui.view">
<field name="name">product.template.search.inherit</field>
<field name="model">product.template</field>
diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml
index f6e5a1a4..3e609f15 100755
--- a/indoteknik_custom/views/purchase_order.xml
+++ b/indoteknik_custom/views/purchase_order.xml
@@ -41,11 +41,12 @@
<field name="incoterm_id" position="after">
<field name="amount_total_without_service"/>
<field name="delivery_amt"/>
+ <field name="approve_by"/>
</field>
<field name="currency_id" position="after">
<field name="summary_qty_po"/>
<field name="count_line_product"/>
- <field name="payment_term_id" readonly="1"/>
+ <field name="payment_term_id"/>
</field>
<field name="amount_total" position="after">
<field name="total_margin"/>
@@ -64,10 +65,14 @@
<field name="qty_onhand" readonly="1" optional="hide"/>
<field name="qty_incoming" readonly="1" optional="hide"/>
<field name="qty_outgoing" readonly="1" optional="hide"/>
+ <field name="clean_website_description_product" readonly="1" optional="hide"/>
<field name="qty_available" readonly="1" optional="hide"/>
<field name="qty_reserved" readonly="1" optional="hide"/>
<field name="suggest" readonly="1" widget="badge" decoration-danger="suggest == 'harus beli'" decoration-success="suggest == 'masih cukup'"/>
- <!-- <field name="suggest" readonly="1"/> -->
+ <!-- <field name="suggest" readonly="1"/> -->
+ </field>
+ <field name="product_id" position="before">
+ <field name="image_small" readonly="1" optional="hide" widget="image" height="50"/>
</field>
<field name="price_unit" position="after">
<field name="price_vendor" attrs="{'readonly': 1}" optional="hide"/>
diff --git a/indoteknik_custom/views/requisition.xml b/indoteknik_custom/views/requisition.xml
index 652d03d0..b704baaf 100644
--- a/indoteknik_custom/views/requisition.xml
+++ b/indoteknik_custom/views/requisition.xml
@@ -78,6 +78,7 @@
<field name="partner_id" required="1" />
<field name="qty_purchase" required="1" />
<field name="price_unit" required="1" />
+ <field name="taxes_id" readonly="1" />
<field name="subtotal" readonly="1" />
<field name="brand_id" />
</tree>
diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml
index d77e09b8..6d02a86b 100644
--- a/indoteknik_custom/views/res_partner.xml
+++ b/indoteknik_custom/views/res_partner.xml
@@ -29,6 +29,15 @@
<field name="nama_wajib_pajak" position="attributes">
<attribute name="required">1</attribute>
</field>
+ <field name="kota_id" position="attributes">
+ <attribute name="required">0</attribute>
+ </field>
+ <field name="kecamatan_id" position="attributes">
+ <attribute name="required">0</attribute>
+ </field>
+ <field name="kelurahan_id" position="attributes">
+ <attribute name="required">0</attribute>
+ </field>
<field name="npwp" position="attributes">
<attribute name="required">1</attribute>
</field>
diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml
index 17faaa95..98001589 100755
--- a/indoteknik_custom/views/sale_order.xml
+++ b/indoteknik_custom/views/sale_order.xml
@@ -72,6 +72,8 @@
<field name="flash_sale"/>
<field name="margin_after_delivery_purchase"/>
<field name="percent_margin_after_delivery_purchase"/>
+ <field name="total_weight"/>
+ <field name="pareto_status"/>
</field>
<field name="analytic_account_id" position="after">
<field name="customer_type" required="1"/>
@@ -80,6 +82,7 @@
<field name="email" required="1"/>
<field name="unreserve_id"/>
<field name="due_id" readonly="1"/>
+ <field name="vendor_approval_id" readonly="1"/>
<field name="source_id" domain="[('id', 'in', [32, 59, 60, 61])]" required="1"/>
<button name="override_allow_create_invoice"
string="Override Create Invoice"
@@ -116,7 +119,8 @@
</attribute>
</xpath>
<xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_total']" position="after">
- <field name="vendor_id" attrs="{'readonly': [('parent.approval_status', '=', 'approved')]}" domain="[('parent_id', '=', False)]"/>
+ <field name="vendor_id" attrs="{'readonly': [('parent.approval_status', '=', 'approved')]}" domain="[('parent_id', '=', False)]" options="{'no_create':True}"/>
+ <field name="vendor_md_id" optional="hide"/>
<field name="purchase_price" attrs="
{
'readonly': [
@@ -126,13 +130,15 @@
]
}
"/>
- <field name="purchase_tax_id" attrs="{'readonly': [('parent.approval_status', '!=', False)]}" domain="[('type_tax_use','=','purchase')]"/>
+ <field name="purchase_price_md" optional="hide"/>
+ <field name="purchase_tax_id" attrs="{'readonly': [('parent.approval_status', '!=', False)]}" domain="[('type_tax_use','=','purchase')]" options="{'no_create':True}"/>
<field name="item_percent_margin"/>
<field name="item_margin" optional="hide"/>
+ <field name="margin_md" optional="hide"/>
<field name="note" optional="hide"/>
<field name="note_procurement" optional="hide"/>
<field name="vendor_subtotal" optional="hide"/>
- <field name="weight" optional="hide"/>
+ <field name="weight" optional="hide"/>
<field name="amount_voucher_disc" string="Voucher" readonly="1" optional="hide"/>
<field name="order_promotion_id" string="Promotion" readonly="1" optional="hide"/>
</xpath>
@@ -239,6 +245,7 @@
<field name="client_order_ref"/>
<field name="payment_type" optional="hide"/>
<field name="payment_status" optional="hide"/>
+ <field name="pareto_status" optional="hide"/>
</field>
</field>
</record>
@@ -257,6 +264,7 @@
<field name="date_driver_arrival"/>
<field name="payment_type" optional="hide"/>
<field name="payment_status" optional="hide"/>
+ <field name="pareto_status" optional="hide"/>
</field>
</field>
</record>
diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml
index 899d29eb..d713edbc 100644
--- a/indoteknik_custom/views/stock_picking.xml
+++ b/indoteknik_custom/views/stock_picking.xml
@@ -16,6 +16,8 @@
<field name="driver_arrival_date" optional="hide"/>
<field name="note_logistic" optional="hide"/>
<field name="note" optional="hide"/>
+ <field name="date_reserved" optional="hide"/>
+ <field name="state_reserve" optional="hide"/>
</field>
<field name="partner_id" position="after">
<field name="purchase_representative_id"/>
@@ -91,6 +93,8 @@
<field name="status_printed"/>
<field name="printed_sj"/>
<field name="date_printed_sj"/>
+ <field name="printed_sj_retur"/>
+ <field name="date_printed_sj_retur"/>
<field name="date_printed_list"/>
<field name="is_internal_use"
string="Internal Use"
diff --git a/indoteknik_custom/views/user_company_request.xml b/indoteknik_custom/views/user_company_request.xml
index ad121a89..0600aa8e 100644
--- a/indoteknik_custom/views/user_company_request.xml
+++ b/indoteknik_custom/views/user_company_request.xml
@@ -4,7 +4,7 @@
<field name="name">user.company.request.tree</field>
<field name="model">user.company.request</field>
<field name="arch" type="xml">
- <tree create="0" default_order="create_date desc">
+ <tree create="1" default_order="create_date desc">
<field name="user_id"/>
<field name="user_company_id"/>
<field name="user_input"/>
@@ -30,7 +30,8 @@
<group>
<group>
<field name="user_id" readonly="1"/>
- <field name="user_company_id" readonly="1"/>
+ <field name="similar_company_ids" invisible="1"/>
+ <field name="user_company_id" domain="[('id', 'in', similar_company_ids)]"/>
<field name="user_input" readonly="1"/>
<field
name="is_approve"
diff --git a/indoteknik_custom/views/vendor_approval.xml b/indoteknik_custom/views/vendor_approval.xml
new file mode 100644
index 00000000..605edfbf
--- /dev/null
+++ b/indoteknik_custom/views/vendor_approval.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+ <data>
+ <record id="vendor_approval_tree" model="ir.ui.view">
+ <field name="name">vendor.approval.tree</field>
+ <field name="model">vendor.approval</field>
+ <field name="arch" type="xml">
+ <tree default_order="create_date desc" create="0">
+ <field name="number"/>
+ <field name="create_date_so"/>
+ <field name="order_id"/>
+ <field name="partner_id"/>
+ <field name="state"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="vendor_approval_line_tree" model="ir.ui.view">
+ <field name="name">vendor.approval.line.tree</field>
+ <field name="model">vendor.approval.line</field>
+ <field name="arch" type="xml">
+ <tree>
+ <field name="product_id"/>
+ <field name="sales_price"/>
+ <field name="product_uom_qty"/>
+ <field name="sales_tax_id"/>
+ <field name="margin_after"/>
+ <field name="vendor_id"/>
+ <field name="vendor_md_id"/>
+ <field name="purchase_price"/>
+ <field name="purchase_price_md"/>
+ <field name="margin_before"/>
+ <field name="purchase_tax_id"/>
+ <field name="percent_margin_difference"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="vendor_approval_form" model="ir.ui.view">
+ <field name="name">vendor.approval.form</field>
+ <field name="model">vendor.approval</field>
+ <field name="arch" type="xml">
+ <form create="false">
+ <header>
+ <button name="action_approve"
+ string="Approve"
+ type="object"
+ attrs="{'invisible': [('state', 'not in', ['draft'])]}"
+ />
+ <button name="action_reject"
+ string="Reject"
+ type="object"
+ attrs="{'invisible': [('state', 'not in', ['draft'])]}"
+ />
+ </header>
+ <sheet>
+ <group>
+ <group>
+ <field name="partner_id" readonly="1"/>
+ <field name="order_id" readonly="1"/>
+ <field name="state" readonly="1"/>
+ <field name="create_date_so" readonly="1"/>
+ </group>
+ </group>
+ <notebook>
+ <page string="SO Line">
+ <field name="vendor_approval_line" readonly="1"/>
+ </page>
+ </notebook>
+ </sheet>
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
+ </form>
+ </field>
+ </record>
+
+ <record id="vendor_approval_view_search" model="ir.ui.view">
+ <field name="name">vendor.approval.search.view</field> <!-- Made the name more descriptive -->
+ <field name="model">vendor.approval</field>
+ <field name="arch" type="xml">
+ <search string="Search Vendor Approval">
+ <field name="number"/>
+ <field name="partner_id"/>
+ <field name="order_id"/>
+ </search>
+ </field>
+ </record>
+
+ <record id="vendor_approval_action" model="ir.actions.act_window">
+ <field name="name">Vendor Approval</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">vendor.approval</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+ <menuitem
+ id="menu_vendor_approval"
+ name="Vendor Approval"
+ parent="sale.product_menu_catalog"
+ sequence="4"
+ action="vendor_approval_action"
+ />
+ </data>
+</odoo>
diff --git a/indoteknik_custom/views/vit_kota.xml b/indoteknik_custom/views/vit_kota.xml
index 97c7e66c..58c97eb4 100755
--- a/indoteknik_custom/views/vit_kota.xml
+++ b/indoteknik_custom/views/vit_kota.xml
@@ -9,6 +9,7 @@
<field name="name"/>
<field name="jenis"/>
<field name="state_id"/>
+ <field name="is_jabodetabek"/>
</tree>
</field>
</record>
@@ -28,6 +29,7 @@
<group>
<field name="jenis"/>
<field name="state_id"/>
+ <field name="is_jabodetabek"/>
</group>
<group/>
</group>