summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorit-fixcomart <it@fixcomart.co.id>2024-11-13 16:13:18 +0700
committerit-fixcomart <it@fixcomart.co.id>2024-11-13 16:13:18 +0700
commitee8f00646d8a1d6faa1d91c82e9c3778cc8e41f8 (patch)
tree926f1a022fd5ca18ac23f53b32abcd370f0bf5c1
parentbe2bc04768f9f423c66a612f4f183d20e70a3145 (diff)
parente7a18de25926714222159cd9b9281b2fab62063b (diff)
Merge branch 'production' into iman/pengajuan-tempo
# Conflicts: # indoteknik_custom/__manifest__.py # indoteknik_custom/models/__init__.py # indoteknik_custom/security/ir.model.access.csv
-rw-r--r--indoteknik_api/controllers/api_v1/cart.py21
-rw-r--r--indoteknik_api/controllers/api_v1/product.py15
-rw-r--r--indoteknik_api/controllers/api_v1/sale_order.py4
-rw-r--r--indoteknik_api/controllers/api_v1/stock_picking.py2
-rw-r--r--indoteknik_api/models/sale_order.py1
-rwxr-xr-xindoteknik_custom/__manifest__.py1
-rwxr-xr-xindoteknik_custom/models/__init__.py3
-rw-r--r--indoteknik_custom/models/approval_date_doc.py1
-rw-r--r--indoteknik_custom/models/approval_retur_picking.py38
-rw-r--r--indoteknik_custom/models/approval_unreserve.py2
-rw-r--r--indoteknik_custom/models/delivery_order.py10
-rw-r--r--indoteknik_custom/models/logbook_sj.py6
-rwxr-xr-xindoteknik_custom/models/product_template.py94
-rw-r--r--indoteknik_custom/models/promotion/sale_order.py10
-rwxr-xr-xindoteknik_custom/models/purchase_order.py55
-rw-r--r--indoteknik_custom/models/report_stock_forecasted.py54
-rw-r--r--indoteknik_custom/models/requisition.py84
-rwxr-xr-xindoteknik_custom/models/sale_order.py66
-rw-r--r--indoteknik_custom/models/sale_order_line.py12
-rw-r--r--indoteknik_custom/models/sales_order_fullfillment.py18
-rw-r--r--indoteknik_custom/models/solr/product_product.py1
-rw-r--r--indoteknik_custom/models/solr/product_template.py1
-rw-r--r--indoteknik_custom/models/stock_picking.py69
-rw-r--r--indoteknik_custom/models/user_company_request.py2
-rw-r--r--indoteknik_custom/models/vendor_approval.py4
-rw-r--r--indoteknik_custom/models/wati.py50
-rw-r--r--indoteknik_custom/models/website_user_cart.py19
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv3
-rw-r--r--indoteknik_custom/views/approval_retur_picking.xml27
-rw-r--r--indoteknik_custom/views/product_product.xml3
-rwxr-xr-xindoteknik_custom/views/purchase_order.xml1
-rw-r--r--indoteknik_custom/views/requisition.xml22
-rwxr-xr-xindoteknik_custom/views/sale_order.xml22
33 files changed, 569 insertions, 152 deletions
diff --git a/indoteknik_api/controllers/api_v1/cart.py b/indoteknik_api/controllers/api_v1/cart.py
index 2a24b205..7a40b1e2 100644
--- a/indoteknik_api/controllers/api_v1/cart.py
+++ b/indoteknik_api/controllers/api_v1/cart.py
@@ -46,8 +46,25 @@ class Cart(controller.Controller):
def get_cart_count_by_user_id(self, user_id, **kw):
user_id = int(user_id)
query = [('user_id', '=', user_id)]
- carts = request.env['website.user.cart'].search_count(query)
- return self.response(carts)
+ carts = request.env['website.user.cart'].search(query)
+ products_active = []
+ products_inactive = []
+ for cart in carts:
+ if cart.product_id:
+ price = cart.product_id._v2_get_website_price_include_tax()
+ if cart.product_id.active and price > 0:
+ product = cart.with_context(price_for="web").get_products()
+ for product_active in product:
+ products_active.append(product_active)
+ else:
+ product_inactives = cart.with_context(price_for="web").get_products()
+ for inactives in product_inactives:
+ products_inactive.append(inactives)
+ else:
+ program = cart.with_context(price_for="web").get_products()
+ for programs in program:
+ products_active.append(programs)
+ return self.response(len(products_active))
@http.route(PREFIX_USER + 'cart/create-or-update', auth='public', methods=['POST', 'OPTIONS'], csrf=False)
@controller.Controller.must_authorized(private=True, private_key='user_id')
diff --git a/indoteknik_api/controllers/api_v1/product.py b/indoteknik_api/controllers/api_v1/product.py
index 9673b3ef..b68eb0f9 100644
--- a/indoteknik_api/controllers/api_v1/product.py
+++ b/indoteknik_api/controllers/api_v1/product.py
@@ -96,6 +96,21 @@ class Product(controller.Controller):
return self.response(data, headers=[('Cache-Control', 'max-age=600, private')])
+ @http.route(prefix + 'product_variant/<id>/qty_available', auth='public', methods=['GET', 'OPTIONS'])
+ @controller.Controller.must_authorized()
+ def get_product_variant_stock_available_by_id(self, **kw):
+ id = int(kw.get('id'))
+ product = request.env['product.product'].search(
+ [('id', '=', id)], limit=1)
+
+ qty_available = product.free_qty
+
+ data = {
+ 'qty': qty_available,
+ }
+
+ return self.response(data, headers=[('Cache-Control', 'max-age=600, private')])
+
@http.route(prefix + 'product/template/price/<id>', auth='public', methods=['GET', 'OPTIONS'])
def get_product_template_price_by_id(self, **kw):
if not self.authenticate():
diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py
index e7664c79..3e182a2e 100644
--- a/indoteknik_api/controllers/api_v1/sale_order.py
+++ b/indoteknik_api/controllers/api_v1/sale_order.py
@@ -446,7 +446,8 @@ class SaleOrder(controller.Controller):
'company_id': 1,
'order_id': sale_order.id,
'product_id': cart['id'],
- 'product_uom_qty': cart['quantity']
+ 'product_uom_qty': cart['quantity'],
+ 'product_available_quantity': cart['available_quantity']
})
order_line.product_id_change()
order_line.onchange_vendor_id()
@@ -463,6 +464,7 @@ class SaleOrder(controller.Controller):
if len(promotions) > 0:
sale_order.apply_promotion_program()
+ sale_order.add_free_product(promotions)
voucher_code = params['value']['voucher']
voucher = request.env['voucher'].search([('code', '=', voucher_code),('apply_type', 'in', ['all', 'brand'])], limit=1)
diff --git a/indoteknik_api/controllers/api_v1/stock_picking.py b/indoteknik_api/controllers/api_v1/stock_picking.py
index f0c7456d..ea8c6400 100644
--- a/indoteknik_api/controllers/api_v1/stock_picking.py
+++ b/indoteknik_api/controllers/api_v1/stock_picking.py
@@ -123,7 +123,7 @@ class StockPicking(controller.Controller):
params = {'sj_documentation': sj_document,
'paket_documentation': paket_document,
- 'driver_arrival_date': self.time_to_str(datetime.utcnow(), '%Y-%m-%d %H:%M:%S'),
+ 'driver_arrival_date': datetime.utcnow(),
}
picking_data = request.env['stock.picking'].search([('picking_code', '=', picking_code)], limit=1)
diff --git a/indoteknik_api/models/sale_order.py b/indoteknik_api/models/sale_order.py
index 725dbb4b..8e0371a3 100644
--- a/indoteknik_api/models/sale_order.py
+++ b/indoteknik_api/models/sale_order.py
@@ -84,6 +84,7 @@ class SaleOrder(models.Model):
'subtotal': line.price_subtotal
}
product['quantity'] = line.product_uom_qty
+ product['available_quantity'] = line.product_available_quantity
data_with_detail['products'].append(product)
for invoice in sale_order.invoice_ids:
if invoice.state == 'posted':
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py
index 2ec5e720..3ec45699 100755
--- a/indoteknik_custom/__manifest__.py
+++ b/indoteknik_custom/__manifest__.py
@@ -146,6 +146,7 @@
'views/approval_unreserve.xml',
'views/vendor_approval.xml',
'views/find_page.xml',
+ 'views/approval_retur_picking.xml',
'views/user_pengajuan_tempo.xml',
'report/report.xml',
'report/report_banner_banner.xml',
diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py
index a2a445bc..e98d0d41 100755
--- a/indoteknik_custom/models/__init__.py
+++ b/indoteknik_custom/models/__init__.py
@@ -133,4 +133,5 @@ from . import vendor_approval
from . import partner
from . import find_page
from . import user_pengajuan_tempo_line
-from . import user_pengajuan_tempo \ No newline at end of file
+from . import user_pengajuan_tempo
+from . import approval_retur_picking
diff --git a/indoteknik_custom/models/approval_date_doc.py b/indoteknik_custom/models/approval_date_doc.py
index 441ada3d..751bae82 100644
--- a/indoteknik_custom/models/approval_date_doc.py
+++ b/indoteknik_custom/models/approval_date_doc.py
@@ -40,6 +40,7 @@ class ApprovalDateDoc(models.Model):
raise UserError("Hanya Accounting Yang Bisa Approve")
self.check_invoice_so_picking
self.picking_id.driver_departure_date = self.driver_departure_date
+ self.picking_id.date_doc_kirim = self.driver_departure_date
self.state = 'done'
self.approve_date = datetime.utcnow()
self.approve_by = self.env.user.id
diff --git a/indoteknik_custom/models/approval_retur_picking.py b/indoteknik_custom/models/approval_retur_picking.py
new file mode 100644
index 00000000..34c449a8
--- /dev/null
+++ b/indoteknik_custom/models/approval_retur_picking.py
@@ -0,0 +1,38 @@
+from odoo import models, fields
+import logging
+
+_logger = logging.getLogger(__name__)
+
+
+class ApprovalReturPicking(models.TransientModel):
+ _name = 'approval.retur.picking'
+ _description = 'Wizard to add Note Return'
+
+ note_return = fields.Text(string="Note Return")
+
+ def action_confirm_note_return(self):
+ picking_ids = self._context.get('picking_ids')
+ picking = self.env['stock.picking'].browse(picking_ids)
+
+ # Update the note_return field
+ picking.update({
+ 'approval_return_status': 'pengajuan1'
+ })
+
+ # Post a highlighted message to lognote
+ # picking.message_post(
+ # body=f"<div style='background-color: #fdf2e9; border: 1px solid #f5c6cb; padding: 10px;'>"
+ # f"<b>Note Return (Pinned):</b><br>{self.note_return}</div>",
+ # subtype_id=self.env.ref("mail.mt_note").id
+ # )
+
+ return {
+ 'type': 'ir.actions.client',
+ 'tag': 'display_notification',
+ 'params': {
+ 'title': 'Notification',
+ 'message': 'Status pengajuan telah berubah',
+ 'next': {'type': 'ir.actions.act_window_close'},
+ }
+ }
+
diff --git a/indoteknik_custom/models/approval_unreserve.py b/indoteknik_custom/models/approval_unreserve.py
index 07ddda1f..ba8b8da7 100644
--- a/indoteknik_custom/models/approval_unreserve.py
+++ b/indoteknik_custom/models/approval_unreserve.py
@@ -74,7 +74,7 @@ class ApprovalUnreserve(models.Model):
raise UserError("Quantity yang di unreserve melebihi quantity yang ada")
def action_approve(self):
- if self.env.user.id != self.user_id.id:
+ if self.env.user.id != self.user_id.id and not self.env.user.has_group('indoteknik_custom.group_role_it'):
raise UserError("Hanya Sales nya yang bisa approve.")
if self.state != 'waiting_approval':
diff --git a/indoteknik_custom/models/delivery_order.py b/indoteknik_custom/models/delivery_order.py
index be5fd2e0..3473197b 100644
--- a/indoteknik_custom/models/delivery_order.py
+++ b/indoteknik_custom/models/delivery_order.py
@@ -33,11 +33,13 @@ class DeliveryOrder(models.TransientModel):
picking.driver_id = self.env.uid
picking.delivery_tracking_no = line_tracking_no
+ if picking.driver_departure_date:
+ picking.sj_return_date = datetime.utcnow()
+
delivery_type = self.env['delivery.order.line'].get_delivery_type(picking.driver_departure_date, picking.driver_arrival_date)
if delivery_type == 'departure':
picking.driver_departure_date = current_time
elif delivery_type == 'arrival':
- picking.driver_arrival_date = current_time
sale_order = False
if picking.origin:
@@ -103,14 +105,16 @@ class DeliveryOrderLine(models.TransientModel):
delivery_type = self.get_delivery_type(picking.driver_departure_date, picking.driver_arrival_date)
if delivery_type != 'departure':
- self.departure_date = picking.driver_departure_date.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S')
+ if picking.driver_departure_date:
+ self.departure_date = picking.driver_departure_date.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S')
if delivery_type == 'departure':
self.departure_date = current_time
elif delivery_type == 'arrival':
self.arrival_date = current_time
else:
- self.arrival_date = picking.driver_arrival_date.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S')
+ if picking.driver_arrival_date:
+ self.arrival_date = picking.driver_arrival_date.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S')
else:
raise UserError('Nomor DO tidak ditemukan')
diff --git a/indoteknik_custom/models/logbook_sj.py b/indoteknik_custom/models/logbook_sj.py
index f84619ad..9f349882 100644
--- a/indoteknik_custom/models/logbook_sj.py
+++ b/indoteknik_custom/models/logbook_sj.py
@@ -101,14 +101,16 @@ class LogbookSJLine(models.TransientModel):
delivery_type = self.get_delivery_type(picking.driver_departure_date, picking.driver_arrival_date)
if delivery_type != 'departure':
- self.departure_date = picking.driver_departure_date.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S')
+ if picking.driver_departure_date:
+ self.departure_date = picking.driver_departure_date.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S')
if delivery_type == 'departure':
self.departure_date = current_time
elif delivery_type == 'arrival':
self.arrival_date = current_time
else:
- self.arrival_date = picking.driver_arrival_date.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S')
+ if picking.driver_arrival_date:
+ self.arrival_date = picking.driver_arrival_date.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S')
else:
raise UserError('Nomor DO tidak ditemukan')
diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py
index 2ca4925b..25473ab8 100755
--- a/indoteknik_custom/models/product_template.py
+++ b/indoteknik_custom/models/product_template.py
@@ -374,6 +374,9 @@ class ProductProduct(models.Model):
is_edited = fields.Boolean(string='Is Edited')
qty_sold = fields.Float(string='Sold Quantity', compute='_get_qty_sold')
short_spesification = fields.Char(string='Short Spesification')
+ max_qty_reorder = fields.Float(string='Max Qty Reorder', compute='_get_max_qty_reordering_rule')
+ qty_rpo = fields.Float(string='Qty RPO', compute='_get_qty_rpo')
+ plafon_qty = fields.Float(string='Max Plafon', compute='_get_plafon_qty_product')
def _get_clean_website_description(self):
for rec in self:
@@ -389,23 +392,46 @@ class ProductProduct(models.Model):
@api.constrains('active')
def archive_product(self):
for product in self:
+ if self.env.context.get('skip_unpublished_constraint'):
+ continue # Mencegah looping saat dipanggil dari metode lain
+
product_template = product.product_tmpl_id
variants = product_template.product_variant_ids
- if product_template.active and product.active:
- if not product.active and len(variants) == 1:
- product_template.with_context(skip_active_constraint=True).active = False
- product_template.unpublished = True
- elif not product.active and len(variants) > 1:
+ if len(variants) == 1:
+ # Jika hanya ada satu varian, atur status `unpublished` berdasarkan `active`
+ product_template.with_context(skip_unpublished_constraint=True).unpublished = not product.active
+ product.with_context(skip_unpublished_constraint=True).unpublished = not product.active
+ else:
+ if product.active:
+ product.with_context(skip_unpublished_constraint=True).unpublished = False
+ product_template.with_context(skip_unpublished_constraint=True).unpublished = any(variant.active for variant in variants)
+ else:
+ product.with_context(skip_unpublished_constraint=True).unpublished = True
all_inactive = all(not variant.active for variant in variants)
- if all_inactive:
- product_template.with_context(skip_active_constraint=True).active = False
- product_template.unpublished = True
- else:
- continue
- if any(variant.active for variant in variants):
- product_template.unpublished = False
- variants.unpublished = False
+ product_template.with_context(skip_unpublished_constraint=True).unpublished = all_inactive
+
+ @api.constrains('unpublished')
+ def archive_product_unpublished(self):
+ for product in self:
+ if self.env.context.get('skip_active_constraint'):
+ continue # Mencegah looping saat dipanggil dari metode lain
+
+ product_template = product.product_tmpl_id
+ variants = product_template.product_variant_ids
+
+ if len(variants) == 1:
+ # Jika hanya ada satu varian, atur status `unpublished` pada template, tetapi biarkan `active` tetap True
+ product_template.with_context(skip_active_constraint=True).unpublished = product.unpublished
+ else:
+ if not product.unpublished:
+ # Jika `unpublished` adalah False, pastikan `active` tetap True
+ product.with_context(skip_active_constraint=True).active = True
+ product_template.with_context(skip_active_constraint=True).active = any(not variant.unpublished for variant in variants)
+ else:
+ # Jika `unpublished` adalah True, atur template hanya jika semua varian di-unpublished
+ all_unpublished = all(variant.unpublished for variant in variants)
+ product_template.with_context(skip_active_constraint=True).active = not all_unpublished
def update_internal_reference_variants(self, limit=100):
variants = self.env['product.product'].search([
@@ -464,6 +490,16 @@ class ProductProduct(models.Model):
qty = sum(qty_incoming.mapped('product_uom_qty'))
product.qty_incoming_bandengan = qty
+ def _get_qty_incoming_bandengan_with_exclude(self):
+ for product in self:
+ qty_incoming = self.env['stock.move'].search([
+ ('product_id', '=', product.id),
+ ('location_dest_id', 'in', [57, 83]),
+ ('state', 'not in', ['done', 'cancel'])
+ ])
+ qty = sum(qty_incoming.mapped('product_uom_qty'))
+ product.qty_incoming_bandengan = qty
+
def _get_qty_outgoing_bandengan(self):
for product in self:
qty_incoming = self.env['stock.move'].search([
@@ -487,13 +523,41 @@ class ProductProduct(models.Model):
def _get_qty_available_bandengan(self):
for product in self:
qty_available = product.qty_incoming_bandengan + product.qty_onhand_bandengan - product.qty_outgoing_bandengan
- product.qty_available_bandengan = qty_available
+ product.qty_available_bandengan = qty_available or 0
def _get_qty_free_bandengan(self):
for product in self:
qty_free = product.qty_onhand_bandengan - product.qty_outgoing_bandengan
product.qty_free_bandengan = qty_free
-
+
+ def _get_max_qty_reordering_rule(self):
+ for product in self:
+ reordering = self.env['stock.warehouse.orderpoint'].search([
+ ('product_id', '=', product.id)
+ ], limit=1)
+ if not reordering:
+ product.max_qty_reorder = 0
+ else:
+ product.max_qty_reorder = reordering.product_max_qty
+
+ def _get_qty_rpo(self):
+ for product in self:
+ rpo = self.env['v.requisition.match.po'].search([
+ ('product_id', '=', product.id)
+ ], limit=1)
+ if not rpo:
+ product.qty_rpo = 0
+ else:
+ product.qty_rpo = rpo.qty_rpo
+
+ def _get_plafon_qty_product(self):
+ for product in self:
+ qty_available = product.qty_available_bandengan
+ max_qty = product.max_qty_reorder
+ qty_rpo = product.qty_rpo
+ product.plafon_qty = max_qty - qty_available + qty_rpo
+
+
# def write(self, vals):
# if 'solr_flag' not in vals:
# for variant in self:
diff --git a/indoteknik_custom/models/promotion/sale_order.py b/indoteknik_custom/models/promotion/sale_order.py
index cb9a6f92..be820c6f 100644
--- a/indoteknik_custom/models/promotion/sale_order.py
+++ b/indoteknik_custom/models/promotion/sale_order.py
@@ -6,6 +6,16 @@ class SaleOrder(models.Model):
order_promotion_ids = fields.One2many('sale.order.promotion', 'order_id', 'Promotions')
+ def add_free_product(self, promotions):
+ for promotion in promotions:
+ program_line = self.env['promotion.program.line'].browse(promotion['program_line_id'])
+ for free_product in program_line.free_product_ids:
+ self.env['sale.order.line'].create({
+ 'order_id': self.id,
+ 'name': "Free Product " + free_product.product_id.display_name,
+ 'display_type': 'line_note'
+ })
+
def apply_promotion_program(self):
userdata = {
'user_id': self.partner_id.user_id.id,
diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py
index 393fc562..3397616d 100755
--- a/indoteknik_custom/models/purchase_order.py
+++ b/indoteknik_custom/models/purchase_order.py
@@ -72,6 +72,8 @@ class PurchaseOrder(models.Model):
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')
+ exclude_incoming = fields.Boolean(string='Exclude Incoming', default=False,
+ help='Centang jika tidak mau masuk perhitungan Incoming Qty')
def _compute_total_margin_match(self):
for purchase in self:
@@ -583,6 +585,16 @@ class PurchaseOrder(models.Model):
picking.scheduled_date = self.date_planned
picking.date_deadline = self.date_planned
+ def _check_qty_plafon_product(self):
+ for line in self.order_line:
+ if not line.product_id:
+ continue
+ # test = line.product_uom_qty
+ # test2 = line.product_id.plafon_qty
+ # test3 = test2 + line.product_uom_qty
+ if line.product_uom_qty > line.product_id.plafon_qty + line.product_uom_qty and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'):
+ raise UserError('Product '+line.product_id.name+' melebihi plafon, harus Approval MD')
+
def button_confirm(self):
res = super(PurchaseOrder, self).button_confirm()
current_time = datetime.now()
@@ -595,7 +607,7 @@ class PurchaseOrder(models.Model):
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.has_group('indoteknik_custom.group_role_merchandiser') and not self.env.user.is_leader:
+ if not self.matches_so 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
@@ -635,7 +647,8 @@ class PurchaseOrder(models.Model):
self.date_planned = delta_time
self.date_deadline_ref_date_planned()
self.unlink_purchasing_job_state()
-
+
+ self._check_qty_plafon_product()
return res
@@ -723,14 +736,46 @@ class PurchaseOrder(models.Model):
template.send_mail(self.id, force_send=True)
def po_approve(self):
- 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.amount_untaxed >= 50000000 and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'):
+ # raise UserError("Hanya Merchandiser yang bisa approve")
+ greater_than_plafon, message = self._get_msg_plafon_qty()
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:
+ elif self.total_percent_margin == self.total_so_percent_margin and self.matches_so and not greater_than_plafon:
raise UserError("Bisa langsung Confirm")
else:
+ reason = ''
self.approval_status = 'pengajuan1'
+ if self.amount_untaxed >= 50000000:
+ reason = 'above 50jt, '
+ if self.total_percent_margin < self.total_so_percent_margin:
+ reason += 'diff margin, '
+ if not self.from_apo and not self.matches_so:
+ reason += 'not link with pj and reorder, '
+ if not self.matches_so:
+ reason += 'not link with so, '
+ # Check Plafon Qty and Get Message every Line Product
+ if greater_than_plafon:
+ reason += message
+ # Post a highlighted message to lognote
+ self.message_post(
+ body=f"<div style='background-color: #fdf2e9; border: 1px solid #f5c6cb; padding: 10px;'>"
+ f"<b>Note (Pinned):</b><br>{reason}</div>",
+ subtype_id=self.env.ref("mail.mt_note").id
+ )
+
+ def _get_msg_plafon_qty(self):
+ message = ''
+ greater_than_plafon = False
+ for line in self.order_line:
+ if not line.product_id:
+ continue
+ if line.product_uom_qty > line.product_id.plafon_qty:
+ message = (message + '\n'+line.product_id.default_code + ' melebihi plafon ('
+ + str(line.product_id.plafon_qty) + ') vs Qty PO ('+str(line.product_uom_qty)+')'
+ + ', ')
+ greater_than_plafon = True
+ return greater_than_plafon, message
def re_calculate(self):
if self.from_apo:
diff --git a/indoteknik_custom/models/report_stock_forecasted.py b/indoteknik_custom/models/report_stock_forecasted.py
index ebdc7d4a..c9d54a15 100644
--- a/indoteknik_custom/models/report_stock_forecasted.py
+++ b/indoteknik_custom/models/report_stock_forecasted.py
@@ -6,33 +6,33 @@ class ReplenishmentReport(models.AbstractModel):
@api.model
def _get_report_lines(self, product_template_ids, product_variant_ids, wh_location_ids):
lines = super(ReplenishmentReport, self)._get_report_lines(product_template_ids, product_variant_ids, wh_location_ids)
- result_dict = {}
-
- for line in lines:
- document_out = line.get('document_out')
-
- if document_out and "SO/" in document_out.name:
- order_id = document_out.id
- if document_out == False:
- continue
- product_id = line.get('product', {}).get('id')
- query = [('product_id', '=', product_id)]
-
- if order_id:
- result = self._calculate_result(line)
- quantity = line.get('quantity', 0)
- result_dict.setdefault(order_id, []).append((result, quantity))
-
- for order_id, results in result_dict.items():
- sales_order = self.env['sale.order'].browse(order_id)
-
- for result, quantity in results:
- self.env['sales.order.fullfillment'].create({
- 'sales_order_id': sales_order.id,
- 'product_id': product_id,
- 'reserved_from': result,
- 'qty_fullfillment': quantity,
- })
+ # result_dict = {}
+ #
+ # for line in lines:
+ # document_out = line.get('document_out')
+ #
+ # if document_out and "SO/" in document_out.name:
+ # order_id = document_out.id
+ # if document_out == False:
+ # continue
+ # product_id = line.get('product', {}).get('id')
+ # query = [('product_id', '=', product_id)]
+ #
+ # if order_id:
+ # result = self._calculate_result(line)
+ # quantity = line.get('quantity', 0)
+ # result_dict.setdefault(order_id, []).append((result, quantity))
+ #
+ # for order_id, results in result_dict.items():
+ # sales_order = self.env['sale.order'].browse(order_id)
+ #
+ # for result, quantity in results:
+ # self.env['sales.order.fullfillment'].create({
+ # 'sales_order_id': sales_order.id,
+ # 'product_id': product_id,
+ # '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 c4104ec5..32a9f94f 100644
--- a/indoteknik_custom/models/requisition.py
+++ b/indoteknik_custom/models/requisition.py
@@ -1,4 +1,4 @@
-from odoo import models, fields, api, _
+from odoo import models, fields, api, tools, _
from odoo.exceptions import UserError
from datetime import datetime
import math
@@ -7,6 +7,33 @@ import logging
_logger = logging.getLogger(__name__)
+class RequisitionMatchPO(models.Model):
+ _name = 'v.requisition.match.po'
+ _auto = False
+ _rec_name = 'product_id'
+
+ id = fields.Integer(string='ID')
+ product_id = fields.Many2one('product.product', string='Product')
+ qty_rpo = fields.Float(string='Qty RPO', help='Qty RPO yang sudah di PO namun SO masih Draft')
+
+ def init(self):
+ tools.drop_view_if_exists(self.env.cr, self._table)
+ self.env.cr.execute("""
+ create or replace view %s as
+ select rl.product_id as id, rl.product_id, sum(rl.qty_purchase) as qty_rpo
+ from requisition_line rl
+ join requisition r on r.id = rl.requisition_id
+ join requisition_purchase_match rpm on rpm.requisition_id = r.id
+ join purchase_order po on po.id = rpm.order_id
+ join sale_order so on so.id = r.sale_order_id
+ where 1=1
+ and r.date_doc >= '2024-11-11'
+ and po.state in ('done', 'purchase')
+ and so.state in ('draft', 'sent')
+ group by rl.product_id
+ """ % self._table)
+
+
class Requisition(models.Model):
_name = 'requisition'
_order = 'id desc'
@@ -20,21 +47,61 @@ class Requisition(models.Model):
notification = fields.Char(string='Notification')
is_po = fields.Boolean(string='Is PO')
requisition_match = fields.One2many('requisition.purchase.match', 'requisition_id', string='Matches', auto_join=True)
- sale_order_id = fields.Many2one('sale.order', string='SO', help='harus diisi nomor SO yang ingin digenerate',
- domain="[('state', '=', 'sale')]")
+ sale_order_id = fields.Many2one('sale.order', string='SO', help='harus diisi nomor SO yang ingin digenerate')
+ sales_approve = fields.Boolean(string='Sales Approve', tracking=3, copy=False)
+ merchandise_approve = fields.Boolean(string='Merchandise Approve', tracking=3, copy=False)
+
+ def generate_requisition_from_so(self):
+ state = ['done', 'sale']
+ if not self.sale_order_id:
+ raise UserError('Sale Order Wajib Diisi dan Harus Draft')
+ if self.sale_order_id.state in state:
+ raise UserError('SO sudah Confirm, akan berakibat double Purchase melalui PJ')
+ if not self.sale_order_id.order_line:
+ raise UserError('Line SO masih kosong, harus diisi dulu')
+ for order_line in self.sale_order_id.order_line:
+ param = {
+ 'requisition_id': self.id,
+ 'product_id': order_line.product_id.id,
+ 'partner_id': order_line.vendor_id.id,
+ 'qty_purchase': order_line.product_uom_qty,
+ 'price_unit': order_line.purchase_price,
+ 'taxes_id': order_line.purchase_tax_id.id,
+ 'subtotal': order_line.purchase_price * order_line.product_uom_qty,
+ 'brand_id': order_line.product_id.x_manufacture.id
+ }
+ self.env['requisition.line'].create([param])
@api.model
def create(self, vals):
vals['number'] = self.env['ir.sequence'].next_by_code('requisition') or '0'
result = super(Requisition, self).create(vals)
return result
-
+
+ def button_approve(self):
+ state = ['done', 'sale']
+ if self.sale_order_id.state in state:
+ raise UserError('SO sudah Confirm, akan berakibat double Purchase melalui PJ')
+ if self.env.user.id not in [377, 19]:
+ raise UserError('Hanya Vita dan Darren Yang Bisa Approve')
+ if self.env.user.id == 377:
+ self.sales_approve = True
+ elif self.env.user.id == 19:
+ if not self.sales_approve:
+ raise UserError('Vita Belum Approve')
+ self.merchandise_approve = True
def create_po_from_requisition(self):
+ if not self.sales_approve:
+ raise UserError('Harus Di Approve oleh Vita')
+ if not self.merchandise_approve:
+ raise UserError('Harus Di Approve oleh Darren')
if not self.requisition_lines:
raise UserError('Tidak ada Lines, belum bisa create PO')
if self.is_po:
raise UserError('Sudah pernah di create PO')
+ if not self.sale_order_id:
+ raise UserError('Tidak ada link dengan Sales Order, tidak bisa dihitung sebagai Plafon Qty di PO')
vendor_ids = self.env['requisition.line'].read_group([
('requisition_id', '=', self.id),
@@ -101,13 +168,20 @@ class Requisition(models.Model):
'product_id': product.id,
'product_qty': line.qty_purchase,
'product_uom_qty': line.qty_purchase,
- 'name': product.name,
+ 'name': product.display_name,
'price_unit': line.price_unit,
'taxes_id': tax,
}
new_po_line = self.env['purchase.order.line'].create([param_line])
line.current_po_id = new_po.id
line.current_po_line_id = new_po_line.id
+
+ self.env['requisition.purchase.match'].create([{
+ 'requisition_id': self.id,
+ 'order_id': new_po.id
+ }])
+ self.is_po = True
+
return po_ids
# def create_po_from_requisition(self):
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index 1ad08154..75332996 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -11,6 +11,7 @@ _logger = logging.getLogger(__name__)
class SaleOrder(models.Model):
_inherit = "sale.order"
+ fulfillment_line_v2 = fields.One2many('sales.order.fulfillment.v2', 'sale_order_id', string='Fullfillment2')
fullfillment_line = fields.One2many('sales.order.fullfillment', 'sales_order_id', string='Fullfillment')
reject_line = fields.One2many('sales.order.reject', 'sale_order_id', string='Reject Lines')
order_sales_match_line = fields.One2many('sales.order.purchase.match', 'sales_order_id', string='Purchase Match Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True)
@@ -78,7 +79,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)
+ vendor_approval_id = fields.Many2one('vendor.approval', string="Vendor Approval", readonly=True, tracking=True, copy=False)
customer_type = fields.Selection([
('pkp', 'PKP'),
('nonpkp', 'Non PKP')
@@ -148,7 +149,7 @@ class SaleOrder(models.Model):
missing_weight_products = []
for line in self.order_line:
- if line.weight:
+ if line.weight > 0:
total_weight += line.weight * line.product_uom_qty
self.total_weight = total_weight
@@ -161,7 +162,7 @@ class SaleOrder(models.Model):
missing_weight_products = []
for line in self.order_line:
- if line.weight:
+ if line.weight > 0:
total_weight += line.weight * line.product_uom_qty
line.product_id.weight = line.weight
else:
@@ -186,7 +187,7 @@ class SaleOrder(models.Model):
missing_weight_products = []
for line in self.order_line:
- if line.weight:
+ if line.weight > 0:
total_weight += line.weight * line.product_uom_qty
line.product_id.weight = line.weight
else:
@@ -346,10 +347,10 @@ class SaleOrder(models.Model):
def _compute_fullfillment(self):
for rec in self:
- rec.fullfillment_line.unlink()
-
- for line in rec.order_line:
- line._compute_reserved_from()
+ # rec.fullfillment_line.unlink()
+ #
+ # for line in rec.order_line:
+ # line._compute_reserved_from()
rec.compute_fullfillment = True
@@ -490,8 +491,8 @@ 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')
+ # 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
@@ -625,19 +626,19 @@ class SaleOrder(models.Model):
# return ['&', ('order_line.invoice_lines.move_id.move_type', 'in', ('out_invoice', 'out_refund')), ('order_line.invoice_lines.move_id', operator, value)]
- def check_data_real_delivery_address(self):
- real_delivery_address = self.real_shipping_id
+ # 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.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')
@api.onchange('partner_id')
def onchange_partner_contact(self):
@@ -768,7 +769,28 @@ class SaleOrder(models.Model):
self._validate_order()
for order in self:
order.order_line.validate_line()
+ order.check_data_real_delivery_address()
+ order._validate_order()
+ order.sale_order_check_approve()
+ main_parent = order.partner_id.get_main_parent()
+ SYSTEM_UID = 25
+ FROM_WEBSITE = order.create_uid.id == SYSTEM_UID
+
+ if FROM_WEBSITE and main_parent.use_so_approval and order.web_approval not in ['cust_procurement','cust_director']:
+ raise UserError("This order not yet approved by customer procurement or director")
+
+ if not order.client_order_ref and order.create_date > datetime(2024, 6, 27):
+ raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
+
+ if not order.commitment_date and order.create_date > datetime(2024, 9, 12):
+ raise UserError("Expected Delivery Date kosong, wajib diisi")
+
+ if not order.real_shipping_id:
+ UserError('Real Delivery Address harus di isi')
+
+ if order.validate_partner_invoice_due():
+ return self._create_notification_action('Notification','Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension')
term_days = 0
for term_line in order.payment_term_id.line_ids:
diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py
index 5e01067a..5a6640ec 100644
--- a/indoteknik_custom/models/sale_order_line.py
+++ b/indoteknik_custom/models/sale_order_line.py
@@ -31,6 +31,7 @@ class SaleOrderLine(models.Model):
vendor_subtotal = fields.Float(string='Vendor Subtotal', compute="_compute_vendor_subtotal")
amount_voucher_disc = fields.Float(string='Voucher Discount')
qty_reserved = fields.Float(string='Qty Reserved', compute='_compute_qty_reserved')
+ product_available_quantity = fields.Float(string='Qty pickup by user',)
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')
@@ -251,12 +252,9 @@ 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
@@ -270,6 +268,14 @@ class SaleOrderLine(models.Model):
line.name = line_name
line.weight = line.product_id.weight
+ @api.constrains('vendor_id')
+ def _check_vendor_id(self):
+ for line in self:
+ price, taxes, vendor_id = self._get_purchase_price(line.product_id)
+ line.vendor_md_id = vendor_id if vendor_id else None
+ line.margin_md = line.item_percent_margin
+ line.purchase_price_md = price
+
def compute_delivery_amt_line(self):
for line in self:
try:
diff --git a/indoteknik_custom/models/sales_order_fullfillment.py b/indoteknik_custom/models/sales_order_fullfillment.py
index ab416e8d..05a0641c 100644
--- a/indoteknik_custom/models/sales_order_fullfillment.py
+++ b/indoteknik_custom/models/sales_order_fullfillment.py
@@ -6,6 +6,24 @@ import logging
_logger = logging.getLogger(__name__)
+class SalesOrderFullfillmentV2(models.Model):
+ _name = 'sales.order.fulfillment.v2'
+
+ sale_order_id = fields.Many2one('sale.order', string='Sale Order')
+ sale_order_line_id = fields.Many2one('sale.order.line', string='Sale Order Line')
+ picking_id = fields.Many2one('stock.picking', string='Picking')
+ move_id = fields.Many2one('stock.move', string='Move')
+ move_line_id = fields.Many2one('stock.move.line', string='Move Line')
+ product_id = fields.Many2one('product.product', string='Product')
+ so_qty = fields.Float(string='SO Qty')
+ reserved_stock_qty = fields.Float(string='Reserved Stock Qty', help='Sudah ter-reserved oleh sistem')
+ delivered_qty = fields.Float(string='Delivered Qty', help='Yang sudah terkirim ke Customer')
+ po_ids = fields.Many2many('purchase.order', string='Purchase Order', help='PO yang dibuat, bisa lebih dari satu')
+ po_qty = fields.Float(string='PO Qty', help='Totalan dari semua PO Outstanding')
+ received_qty = fields.Float(string='Received Qty', help='Totalan dari barang yang diterima dari PO tsb')
+ purchaser = fields.Char(string='Purchaser')
+
+
class SalesOrderFullfillment(models.Model):
_name = 'sales.order.fullfillment'
diff --git a/indoteknik_custom/models/solr/product_product.py b/indoteknik_custom/models/solr/product_product.py
index 7c10a910..dd1d40f6 100644
--- a/indoteknik_custom/models/solr/product_product.py
+++ b/indoteknik_custom/models/solr/product_product.py
@@ -67,6 +67,7 @@ class ProductProduct(models.Model):
'product_id_i': variant.id,
'template_id_i': variant.product_tmpl_id.id,
'image_s': ir_attachment.api_image('product.template', 'image_512', variant.product_tmpl_id.id),
+ 'image_mobile_s': ir_attachment.api_image('product.template', 'image_256', variant.product_tmpl_id.id),
'stock_total_f': variant.qty_stock_vendor,
'weight_f': variant.weight,
'manufacture_id_i': variant.product_tmpl_id.x_manufacture.id or 0,
diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py
index 1d54cc9b..87e8370f 100644
--- a/indoteknik_custom/models/solr/product_template.py
+++ b/indoteknik_custom/models/solr/product_template.py
@@ -91,6 +91,7 @@ class ProductTemplate(models.Model):
"product_rating_f": template.virtual_rating,
"product_id_i": template.id,
"image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id),
+ 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id),
"variant_total_i": template.product_variant_count,
"stock_total_f": template.qty_stock_vendor,
"weight_f": template.weight,
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index 4c9d7658..50e9304b 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -104,6 +104,7 @@ class StockPicking(models.Model):
('to invoice', 'To Invoice'),
('no', 'Nothing to Invoice')
], string='Invoice Status', related="sale_id.invoice_status")
+ note_return = fields.Text(string="Note Return", help="Catatan untuk kirim barang kembali")
state_reserve = fields.Selection([
('waiting', 'Waiting For Fullfilment'),
@@ -192,9 +193,9 @@ class StockPicking(models.Model):
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
+ # @api.constrains('driver_departure_date')
+ # def constrains_driver_departure_date(self):
+ # self.date_doc_kirim = self.driver_departure_date
@api.constrains('arrival_time')
def constrains_arrival_time(self):
@@ -246,25 +247,22 @@ class StockPicking(models.Model):
# break
def check_state_reserve(self):
- picking = self.search([
+ pickings = 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)
+ for picking in pickings:
+ fullfillments = self.env['sales.order.fullfillment'].search([
+ ('sales_order_id', '=', picking.sale_id.id)
])
-
- data.state_reserve = 'ready'
- if not data.date_reserved:
- 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
+
+ picking.state_reserve = 'ready'
+ picking.date_reserved = picking.date_reserved or datetime.datetime.utcnow()
+
+ if any(rec.reserved_from not in ['Inventory On Hand', 'Reserved from stock', 'Free Stock'] for rec in fullfillments):
+ picking.state_reserve = 'waiting'
+ picking.date_reserved = ''
def _create_approval_notification(self, approval_role):
title = 'Warning'
@@ -444,9 +442,28 @@ class StockPicking(models.Model):
if self.env.user.is_accounting:
pick.approval_return_status = 'approved'
else:
- pick.approval_return_status = 'pengajuan1'
+ if self.picking_type_code == 'outgoing':
+ if self.env.user.id in [3988, 3401, 20]:
+ action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_stock_return_note_wizard')
+ action['context'] = {
+ 'picking_ids': [x.id for x in self]
+ }
+ return action
+ else:
+ raise UserError('Harus Sales Admin yang Ask Return')
+ elif self.picking_type_code == 'incoming':
+ if self.env.user.has_group('indoteknik_custom.group_role_purchasing'):
+ action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_stock_return_note_wizard')
+ action['context'] = {
+ 'picking_ids': [x.id for x in self]
+ }
+ return action
+ else:
+ raise UserError('Harus Purchasing yang Ask Return')
+
def calculate_line_no(self):
+
for picking in self:
name = picking.group_id.name
for move in picking.move_ids_without_package:
@@ -503,6 +520,10 @@ class StockPicking(models.Model):
def button_validate(self):
+ if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking':
+ if self.origin and 'Return of' in self.origin:
+ raise UserError("Button ini hanya untuk Logistik")
+
if self._name != 'stock.picking':
return super(StockPicking, self).button_validate()
@@ -553,6 +574,14 @@ class StockPicking(models.Model):
self.date_done = datetime.datetime.utcnow()
self.state_reserve = 'done'
return res
+ def action_cancel(self):
+ if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking':
+ if self.origin and 'Return of' in self.origin:
+ raise UserError("Button ini hanya untuk Logistik")
+
+ res = super(StockPicking, self).action_cancel()
+ return res
+
@api.model
def create(self, vals):
@@ -688,4 +717,4 @@ class StockPicking(models.Model):
formatted_fastest_eta = fastest_eta.strftime(format_time_fastest)
formatted_longest_eta = longest_eta.strftime(format_time)
- return f'{formatted_fastest_eta} - {formatted_longest_eta}'
+ return f'{formatted_fastest_eta} - {formatted_longest_eta}' \ No newline at end of file
diff --git a/indoteknik_custom/models/user_company_request.py b/indoteknik_custom/models/user_company_request.py
index 86e66934..dd9a35c1 100644
--- a/indoteknik_custom/models/user_company_request.py
+++ b/indoteknik_custom/models/user_company_request.py
@@ -74,7 +74,7 @@ class UserCompanyRequest(models.Model):
if not self.is_approve and is_approve:
if is_approve == 'approved':
- self.user_id.parent_id = self.user_company_id.id
+ self.user_id.parent_id = vals.get('user_company_id') if vals.get('user_company_id') else self.user_company_id.id
self.user_id.customer_type = self.user_company_id.customer_type
self.user_id.npwp = self.user_company_id.npwp
self.user_id.sppkp = self.user_company_id.sppkp
diff --git a/indoteknik_custom/models/vendor_approval.py b/indoteknik_custom/models/vendor_approval.py
index b0d58b85..bcc5d3ea 100644
--- a/indoteknik_custom/models/vendor_approval.py
+++ b/indoteknik_custom/models/vendor_approval.py
@@ -30,10 +30,10 @@ class VendorApproval(models.Model):
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)
-
+ if not self.order_id.due_id:
+ self.order_id.action_confirm()
def action_reject(self):
if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'):
diff --git a/indoteknik_custom/models/wati.py b/indoteknik_custom/models/wati.py
index d9fb7247..eed5413e 100644
--- a/indoteknik_custom/models/wati.py
+++ b/indoteknik_custom/models/wati.py
@@ -192,27 +192,6 @@ class WatiHistory(models.Model):
is_get_attribute = fields.Boolean(string='Get Attribute', default=False)
def _get_attribute_wati(self):
- # url = 'https://live-server-2106.wati.io/api/v1/getContacts'
-
- # cookies = {
- # 'affinity': '1701232090.884.1520.321410|ff187ffce9bc0bae13542bb446e41008',
- # }
-
- # headers = {
- # 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3MGM5ZmJhNy00MWRlLTRkMWEtYjY2NS1hM2Q5ODc2ZjhlZWIiLCJ1bmlxdWVfbmFtZSI6InR5YXNAaW5kb3Rla25pay5jb20iLCJuYW1laWQiOiJ0eWFzQGluZG90ZWtuaWsuY29tIiwiZW1haWwiOiJ0eWFzQGluZG90ZWtuaWsuY29tIiwiYXV0aF90aW1lIjoiMTEvMjkvMjAyMyAwNDoxNzo0NyIsImRiX25hbWUiOiIyMTA2IiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQURNSU5JU1RSQVRPUiIsImV4cCI6MjUzNDAyMzAwODAwLCJpc3MiOiJDbGFyZV9BSSIsImF1ZCI6IkNsYXJlX0FJIn0.--KHv4GCOG2MM3lNW9Nm-0-d8OAVpn5kbcSX4JKqATQ',
- # # 'Cookie': 'affinity=1701232090.884.1520.321410|ff187ffce9bc0bae13542bb446e41008',
- # }
-
- # files = {
- # 'pageSize': (None, '1'),
- # 'pageNumber': (None, '1'),
- # 'name': (None, ''),
- # 'attribute': (None, '[{name: "phone", operator: "contain", value: "6285751430014"}]'),
- # 'createdDate': (None, ''),
- # }
-
- # response = requests.get(url, cookies=cookies, headers=headers, files=files)
- # print(response.json())
domain = [
'&',
('is_get_attribute', '=', False),
@@ -226,29 +205,36 @@ class WatiHistory(models.Model):
for wati_history in wati_histories:
count += 1
_logger.info('[Parse Notification] Process: %s/%s' % (str(count), str(limit)))
+
wati_api = self.env['wati.api']
+
+ # Perbaikan pada params 'attribute' untuk menghindari masalah "type object is not subscriptable"
params = {
- 'pageSize':1,
- 'pageNumber':1,
- 'attribute':[{'name': "phone", 'operator': "contain", 'value': wati_history.wa_id}],
+ 'pageSize': 1,
+ 'pageNumber': 1,
+ 'attribute': json.dumps([{'name': "phone", 'operator': "contain", 'value': wati_history.wa_id}]),
}
+
wati_contacts = wati_api.http_get('/api/v1/getContacts', params)
- if wati_contacts['result'] != 'success':
+
+ if wati_contacts.get('result') != 'success':
return
- json_dump = json.dumps(wati_contacts, indent=4, sort_keys=True)
- contact_list = json.loads(json_dump)['contact_list']
+
+ contact_list = wati_contacts.get('contact_list', [])
+
perusahaan = email = ''
for data in contact_list:
- custom_params = data['customParams']
+ custom_params = data.get('customParams', [])
for custom_param in custom_params:
- name = custom_param['name']
- value = custom_param['value']
+ name = custom_param.get('name')
+ value = custom_param.get('value')
if name == 'perusahaan':
perusahaan = value
elif name == 'email':
email = value
- # end for 2
- # end for 1
+ # End inner loop
+
+ # Update wati_history fields
wati_history.perusahaan = perusahaan
wati_history.email = email
wati_history.is_get_attribute = True
diff --git a/indoteknik_custom/models/website_user_cart.py b/indoteknik_custom/models/website_user_cart.py
index 26e217cb..494f32f3 100644
--- a/indoteknik_custom/models/website_user_cart.py
+++ b/indoteknik_custom/models/website_user_cart.py
@@ -65,9 +65,11 @@ class WebsiteUserCart(models.Model):
if stock_quant:
res['is_in_bu'] = True
res['on_hand_qty'] = sum(stock_quant.mapped('quantity'))
+ res['available_quantity'] = stock_quant.available_quantity
else:
res['is_in_bu'] = False
res['on_hand_qty'] = 0
+ res['available_quantity'] = 0
flashsales = self.product_id._get_active_flash_sale()
res['has_flashsale'] = True if len(flashsales) > 0 else False
@@ -93,22 +95,31 @@ class WebsiteUserCart(models.Model):
def get_product_by_user(self, user_id, selected=False, source=False):
user_id = int(user_id)
-
+
if source == 'buy':
source = ['buy']
else:
source = ['add_to_cart', 'buy']
parameters = [
- ('user_id', '=', user_id),
+ ('user_id', '=', user_id),
('source', 'in', source)
]
+ carts = self.search(parameters)
+
+ for cart in carts:
+ if cart.product_id:
+ price = cart.product_id._v2_get_website_price_include_tax()
+ if not cart.product_id.active or price < 1:
+ cart.is_selected = False
if selected:
parameters.append(('is_selected', '=', True))
- carts = self.search(parameters)
- products = carts.get_products()
+ products_active = self.search(parameters)
+
+ products = products_active.get_products()
+
return products
def get_user_checkout(self, user_id, voucher=False, voucher_shipping=False, source=False):
diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv
index 1369a03a..b582a587 100755
--- a/indoteknik_custom/security/ir.model.access.csv
+++ b/indoteknik_custom/security/ir.model.access.csv
@@ -144,5 +144,8 @@ access_vendor_approval_line,access.vendor.approval.line,model_vendor_approval_li
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
+access_v_requisition_match_po,access.v.requisition.match.po,model_v_requisition_match_po,,1,1,1,1
+access_approval_retur_picking,access.approval.retur.picking,model_approval_retur_picking,,1,1,1,1
+access_sales_order_fulfillment_v2,access.sales.order.fulfillment.v2,model_sales_order_fulfillment_v2,,1,1,1,1
access_User_pengajuan_tempo_line,access.user.pengajuan.tempo.line,model_user_pengajuan_tempo_line,,1,1,1,1
access_user_pengajuan_tempo,access.user.pengajuan.tempo,model_user_pengajuan_tempo,,1,1,1,1 \ No newline at end of file
diff --git a/indoteknik_custom/views/approval_retur_picking.xml b/indoteknik_custom/views/approval_retur_picking.xml
new file mode 100644
index 00000000..5ce28e20
--- /dev/null
+++ b/indoteknik_custom/views/approval_retur_picking.xml
@@ -0,0 +1,27 @@
+<odoo>
+ <!-- Form View for Stock Return Note Wizard -->
+ <record id="view_stock_return_note_form" model="ir.ui.view">
+ <field name="name">approval.retur.picking.form</field>
+ <field name="model">approval.retur.picking</field>
+ <field name="arch" type="xml">
+ <form string="Add Return Note">
+ <group>
+ <span>Ask Approval Retur?</span>
+ </group>
+ <footer>
+ <button name="action_confirm_note_return" string="Confirm" type="object" class="btn-primary"/>
+ <button string="Cancel" class="btn-secondary" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <!-- Action to Open the Wizard -->
+ <record id="action_stock_return_note_wizard" model="ir.actions.act_window">
+ <field name="name">Add Return Note</field>
+ <field name="res_model">approval.retur.picking</field>
+ <field name="view_mode">form</field>
+ <field name="view_id" ref="view_stock_return_note_form"/>
+ <field name="target">new</field>
+ </record>
+</odoo>
diff --git a/indoteknik_custom/views/product_product.xml b/indoteknik_custom/views/product_product.xml
index c06cc5f1..71748e44 100644
--- a/indoteknik_custom/views/product_product.xml
+++ b/indoteknik_custom/views/product_product.xml
@@ -11,11 +11,14 @@
<field name="incoming_qty"/>
</field>
<field name="virtual_available" position="after">
+ <field name="max_qty_reorder" optional="hide"/>
<field name="qty_onhand_bandengan" optional="hide"/>
<field name="qty_incoming_bandengan" optional="hide"/>
<field name="qty_outgoing_bandengan" optional="hide"/>
<field name="qty_available_bandengan" optional="hide"/>
<field name="qty_free_bandengan" optional="hide"/>
+ <field name="qty_rpo" optional="hide"/>
+ <field name="plafon_qty" optional="hide"/>
</field>
</field>
</record>
diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml
index 06c76a82..0e6b6792 100755
--- a/indoteknik_custom/views/purchase_order.xml
+++ b/indoteknik_custom/views/purchase_order.xml
@@ -212,7 +212,6 @@
<field name="code">model.procure_calculation()</field>
<field name="state">code</field>
<field name="priority">75</field>
- <field name="active">True</field>
</record>
</data>
<data>
diff --git a/indoteknik_custom/views/requisition.xml b/indoteknik_custom/views/requisition.xml
index b704baaf..957113a7 100644
--- a/indoteknik_custom/views/requisition.xml
+++ b/indoteknik_custom/views/requisition.xml
@@ -50,6 +50,13 @@
<field name="model">requisition</field>
<field name="arch" type="xml">
<form>
+ <header>
+ <button name="button_approve"
+ string="Approve"
+ type="object"
+ class="mr-2 oe_highlight"
+ />
+ </header>
<sheet string="Requisition">
<div class="oe_button_box" name="button_box"/>
<group>
@@ -62,12 +69,19 @@
</group>
<group>
<div>
- <button name="create_po_from_requisition"
- string="Create PO"
- type="object"
- class="mr-2 oe_highlight"
+ <button name="generate_requisition_from_so"
+ string="Create Line from SO"
+ type="object"
+ class="mr-2 oe_highlight"
/>
</div>
+ <div>
+ <button name="create_po_from_requisition"
+ string="Create PO"
+ type="object"
+ class="mr-2 oe_highlight"
+ />
+ </div>
</group>
</group>
<notebook>
diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml
index 98001589..b8f2d08d 100755
--- a/indoteknik_custom/views/sale_order.xml
+++ b/indoteknik_custom/views/sale_order.xml
@@ -227,6 +227,9 @@
<page string="Fullfillment" name="page_sale_order_fullfillment">
<field name="fullfillment_line" readonly="1"/>
</page>
+ <page string="Fulfillment v2" name="page_sale_order_fullfillment2">
+ <field name="fulfillment_line_v2" readonly="1"/>
+ </page>
<page string="Reject Line" name="page_sale_order_reject_line">
<field name="reject_line" readonly="1"/>
</page>
@@ -342,6 +345,25 @@
</data>
<data>
+
+ </data>
+ <record id="sales_order_fulfillment_v2_tree" model="ir.ui.view">
+ <field name="name">sales.order.fulfillment.v2.tree</field>
+ <field name="model">sales.order.fulfillment.v2</field>
+ <field name="arch" type="xml">
+ <tree editable="top" create="false">
+ <field name="product_id" readonly="1"/>
+ <field name="so_qty" readonly="1" optional="show"/>
+ <field name="reserved_stock_qty" readonly="1" optional="show"/>
+ <field name="delivered_qty" readonly="1" optional="hide"/>
+ <field name="po_ids" widget="many2many_tags" readonly="1" optional="show"/>
+ <field name="po_qty" readonly="1" optional="show"/>
+ <field name="received_qty" readonly="1" optional="show"/>
+ <field name="purchaser" readonly="1" optional="hide"/>
+ </tree>
+ </field>
+ </record>
+ <data>
<record id="sales_order_fullfillmet_tree" model="ir.ui.view">
<field name="name">sales.order.fullfillment.tree</field>
<field name="model">sales.order.fullfillment</field>