summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indoteknik_api/controllers/api_v1/sale_order.py333
-rw-r--r--indoteknik_api/controllers/api_v1/voucher.py2
-rw-r--r--indoteknik_custom/models/commision.py5
-rw-r--r--indoteknik_custom/models/mrp_production.py8
-rwxr-xr-xindoteknik_custom/models/purchase_order.py146
-rwxr-xr-xindoteknik_custom/models/sale_order.py7
-rw-r--r--indoteknik_custom/models/sale_order_line.py7
-rw-r--r--indoteknik_custom/models/shipment_group.py2
-rw-r--r--indoteknik_custom/models/stock_picking.py10
-rw-r--r--indoteknik_custom/models/tukar_guling.py128
-rw-r--r--indoteknik_custom/models/tukar_guling_po.py88
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv4
-rwxr-xr-xindoteknik_custom/views/purchase_order.xml43
-rw-r--r--indoteknik_custom/views/refund_sale_order.xml199
-rwxr-xr-xindoteknik_custom/views/sale_order.xml2
-rw-r--r--indoteknik_custom/views/stock_picking.xml4
-rw-r--r--indoteknik_custom/views/tukar_guling.xml8
-rw-r--r--indoteknik_custom/views/tukar_guling_po.xml13
18 files changed, 579 insertions, 430 deletions
diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py
index f116f189..0527345e 100644
--- a/indoteknik_api/controllers/api_v1/sale_order.py
+++ b/indoteknik_api/controllers/api_v1/sale_order.py
@@ -3,6 +3,9 @@ from odoo import http
from datetime import datetime, timedelta
from odoo.http import request
import json
+import logging
+
+_logger = logging.getLogger(__name__)
class SaleOrder(controller.Controller):
@@ -465,138 +468,210 @@ class SaleOrder(controller.Controller):
@http.route(PREFIX_PARTNER + 'sale_order/checkout', auth='public', method=['POST', 'OPTIONS'], csrf=False)
@controller.Controller.must_authorized()
def create_partner_sale_order(self, **kw):
- config = request.env['ir.config_parameter']
- product_pricelist_default_discount_id = int(config.get_param('product.pricelist.tier1_v2'))
- user_pricelist = request.env.context.get('user_pricelist').id or False
+ _logger.info("=== START CREATE PARTNER SALE ORDER ===")
+
+ try:
+ config = request.env['ir.config_parameter']
+ product_pricelist_default_discount_id = int(config.get_param('product.pricelist.tier1_v2'))
+ user_pricelist = request.env.context.get('user_pricelist').id or False
+ _logger.info(
+ f"Config - Default Pricelist: {product_pricelist_default_discount_id}, User Pricelist: {user_pricelist}")
+
+ params = self.get_request_params(kw, {
+ 'user_id': ['number'],
+ 'partner_id': ['number'],
+ 'partner_shipping_id': ['required', 'number'],
+ 'partner_invoice_id': ['required', 'number'],
+ 'order_line': ['required', 'default:[]'],
+ 'po_number': [],
+ 'po_file': [],
+ 'type': [],
+ 'delivery_amount': ['number', 'default:0'],
+ 'carrier_id': [],
+ 'delivery_service_type': [],
+ 'flash_sale': ['boolean'],
+ 'note_website': [],
+ 'voucher': [],
+ 'source': [],
+ 'estimated_arrival_days': ['number', 'default:0'],
+ 'estimated_arrival_days_start': ['number', 'default:0']
+ })
- params = self.get_request_params(kw, {
- 'user_id': ['number'],
- 'partner_id': ['number'],
- 'partner_shipping_id': ['required', 'number'],
- 'partner_invoice_id': ['required', 'number'],
- 'order_line': ['required', 'default:[]'],
- 'po_number': [],
- 'po_file': [],
- 'type': [],
- 'delivery_amount': ['number', 'default:0'],
- 'carrier_id': [],
- 'delivery_service_type': [],
- 'flash_sale': ['boolean'],
- 'note_website': [],
- 'voucher': [],
- 'source': [],
- 'estimated_arrival_days': ['number', 'default:0'],
- 'estimated_arrival_days_start': ['number', 'default:0']
- })
-
- if not params['valid']:
- return self.response(code=400, description=params)
+ _logger.info(f"Raw input params: {kw}")
+ _logger.info(f"Processed params: {params}")
- # Fetch partner details
- sales_partner = request.env['res.partner'].browse(params['value']['partner_id'])
- partner_invoice = request.env['res.partner'].browse(params['value']['partner_invoice_id'])
- main_partner = partner_invoice.get_main_parent()
- parameters = {
- 'warehouse_id': 8,
- 'carrier_id': 1,
- 'sales_tax_id': 23,
- 'pricelist_id': user_pricelist or product_pricelist_default_discount_id,
- 'payment_term_id': 26,
- 'team_id': 2,
- 'company_id': 1,
- 'currency_id': 12,
- 'source_id': 59,
- 'state': 'draft',
- 'picking_policy': 'direct',
- 'partner_id': params['value']['partner_id'],
- 'partner_shipping_id': params['value']['partner_shipping_id'],
- 'real_shipping_id': params['value']['partner_shipping_id'],
- 'partner_invoice_id': main_partner.id,
- 'real_invoice_id': params['value']['partner_invoice_id'],
- 'partner_purchase_order_name': params['value']['po_number'],
- 'partner_purchase_order_file': params['value']['po_file'],
- 'delivery_amt': params['value']['delivery_amount'] * 1.10,
- 'estimated_arrival_days': params['value']['estimated_arrival_days'],
- 'estimated_arrival_days_start': params['value']['estimated_arrival_days_start'],
- 'shipping_cost_covered': 'customer',
- 'shipping_paid_by': 'customer',
- 'carrier_id': params['value']['carrier_id'],
- 'delivery_service_type': params['value']['delivery_service_type'],
- 'flash_sale': params['value']['flash_sale'],
- 'note_website': params['value']['note_website'],
- 'customer_type': sales_partner.customer_type if sales_partner else 'nonpkp', # Get Customer Type from partner
- 'npwp': sales_partner.npwp or '0', # Get NPWP from partner
- 'sppkp': sales_partner.sppkp, # Get SPPKP from partner
- 'email': sales_partner.email, # Get Email from partner
- 'user_id': 11314 # User ID: Boy Revandi
- }
-
- sales_partner = request.env['res.partner'].browse(parameters['partner_id'])
- if sales_partner and sales_partner.user_id and sales_partner.user_id.id not in [25]: # 25: System
- parameters['user_id'] = sales_partner.user_id.id
-
- if params['value']['type'] == 'sale_order':
- parameters['approval_status'] = 'pengajuan1'
- sale_order = request.env['sale.order'].with_context(from_website_checkout=True).create([parameters])
- sale_order.onchange_partner_contact()
-
- user_id = params['value']['user_id']
- user_cart = request.env['website.user.cart']
- source = params['value']['source']
- carts = user_cart.get_product_by_user(user_id=user_id, selected=True, source=source)
-
- promotions = []
- for cart in carts:
- if cart['cart_type'] == 'product':
- order_line = request.env['sale.order.line'].create({
- 'company_id': 1,
- 'order_id': sale_order.id,
- 'product_id': cart['id'],
- 'product_uom_qty': cart['quantity'],
- 'product_available_quantity': cart['available_quantity']
- })
- order_line.product_id_change()
- order_line.weight = order_line.product_id.weight
- order_line.onchange_vendor_id()
- order_line.price_unit = cart['price']['price']
- order_line.discount = cart['price']['discount_percentage']
- elif cart['cart_type'] == 'promotion':
- promotions.append({
- 'order_id': sale_order.id,
- 'program_line_id': cart['id'],
- 'quantity': cart['quantity']
- })
-
- sale_order._compute_etrts_date()
+ if not params['valid']:
+ _logger.error(f"Invalid params: {params}")
+ return self.response(code=400, description=params)
- request.env['sale.order.promotion'].create(promotions)
-
- 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)
- voucher_shipping = request.env['voucher'].search([('code', '=', voucher_code),('apply_type', 'in', ['shipping'])], limit=1)
- if voucher and len(promotions) == 0:
- sale_order.voucher_id = voucher.id
- sale_order.apply_voucher()
-
- if voucher_shipping and len(promotions) == 0:
- sale_order.voucher_shipping_id = voucher_shipping.id
- sale_order.apply_voucher_shipping()
-
- cart_ids = [x['cart_id'] for x in carts]
- if sale_order._requires_approval_margin_leader(): #jika ada error tambahkan kondisi if params['value']['type'] == 'sale_order':
- sale_order.approval_status = 'pengajuan2'
- elif sale_order._requires_approval_margin_manager():
- sale_order.approval_status = 'pengajuan1'
- # user_cart.browse(cart_ids).unlink()
- sale_order._auto_set_shipping_from_website()
- return self.response({
- 'id': sale_order.id,
- 'name': sale_order.name
- })
+ # Fetch partner details
+ sales_partner = request.env['res.partner'].browse(params['value']['partner_id'])
+ partner_invoice = request.env['res.partner'].browse(params['value']['partner_invoice_id'])
+ main_partner = partner_invoice.get_main_parent()
+ _logger.info(
+ f"Partner Info - Sales: {sales_partner.id}, Invoice: {partner_invoice.id}, Main: {main_partner.id}")
+
+ parameters = {
+ 'warehouse_id': 8,
+ 'carrier_id': 1,
+ 'sales_tax_id': 23,
+ 'pricelist_id': user_pricelist or product_pricelist_default_discount_id,
+ 'payment_term_id': 26,
+ 'team_id': 2,
+ 'company_id': 1,
+ 'currency_id': 12,
+ 'source_id': 59,
+ 'state': 'draft',
+ 'picking_policy': 'direct',
+ 'partner_id': params['value']['partner_id'],
+ 'partner_shipping_id': params['value']['partner_shipping_id'],
+ 'real_shipping_id': params['value']['partner_shipping_id'],
+ 'partner_invoice_id': main_partner.id,
+ 'real_invoice_id': params['value']['partner_invoice_id'],
+ 'partner_purchase_order_name': params['value']['po_number'],
+ 'partner_purchase_order_file': params['value']['po_file'],
+ 'delivery_amt': params['value']['delivery_amount'] * 1.10,
+ 'estimated_arrival_days': params['value']['estimated_arrival_days'],
+ 'estimated_arrival_days_start': params['value']['estimated_arrival_days_start'],
+ 'shipping_cost_covered': 'customer',
+ 'shipping_paid_by': 'customer',
+ 'carrier_id': params['value']['carrier_id'],
+ 'delivery_service_type': params['value']['delivery_service_type'],
+ 'flash_sale': params['value']['flash_sale'],
+ 'note_website': params['value']['note_website'],
+ 'customer_type': sales_partner.customer_type if sales_partner else 'nonpkp',
+ 'npwp': sales_partner.npwp or '0',
+ 'sppkp': sales_partner.sppkp,
+ 'email': sales_partner.email,
+ 'user_id': 11314
+ }
+ _logger.info(f"Order parameters: {parameters}")
+
+ sales_partner = request.env['res.partner'].browse(parameters['partner_id'])
+ if sales_partner and sales_partner.user_id and sales_partner.user_id.id not in [25]:
+ parameters['user_id'] = sales_partner.user_id.id
+ _logger.info(f"Updated user_id from partner: {parameters['user_id']}")
+
+ if params['value']['type'] == 'sale_order':
+ parameters['approval_status'] = 'pengajuan1'
+ _logger.info("Setting approval_status to 'pengajuan1'")
+
+ sale_order = request.env['sale.order'].with_context(from_website_checkout=True).create([parameters])
+ sale_order.onchange_partner_contact()
+ _logger.info(f"Created SO: {sale_order.id} - {sale_order.name}")
+
+ user_id = params['value']['user_id']
+ user_cart = request.env['website.user.cart']
+ source = params['value']['source']
+ _logger.info(f"Getting cart for user: {user_id}, source: {source}")
+
+ carts = user_cart.get_product_by_user(user_id=user_id, selected=True, source=source)
+ _logger.info(f"Found {len(carts)} cart items")
+
+ promotions = []
+ for idx, cart in enumerate(carts, 1):
+ _logger.info(f"\n=== Processing Cart Item {idx}/{len(carts)} ===")
+ _logger.info(f"Full cart data: {cart}")
+
+ if cart['cart_type'] == 'product':
+ product = request.env['product.product'].browse(cart['id'])
+ _logger.info(f"Product: {product.id} - {product.name}")
+ _logger.info(f"Cart Price Data: {cart['price']}")
+
+ # Determine discount status based on:
+ # 1. has_flashsale flag from cart data
+ # 2. discount percentage > 0
+ # 3. global flash sale parameter
+ is_flash_sale_item = cart.get('has_flashsale', False)
+ discount_percent = float(cart['price'].get('discount_percentage', 0))
+ global_flash_sale = params['value'].get('flash_sale', False)
+
+ is_has_disc = False
+
+ # Item is considered discounted if:
+ # - It's specifically marked as flash sale item, OR
+ # - It has significant discount (>0%) and not affected by global flash sale
+ if is_flash_sale_item:
+ is_has_disc = True
+ _logger.info("Item is flash sale product - marked as discounted")
+ elif discount_percent > 0 and not global_flash_sale:
+ is_has_disc = True
+ _logger.info(f"Item has discount {discount_percent}% - marked as discounted")
+ elif global_flash_sale:
+ _logger.info("Global flash sale active but item not eligible - not marked as discounted")
+
+ _logger.info(f"Final is_has_disc: {is_has_disc}")
+
+ order_line = request.env['sale.order.line'].create({
+ 'company_id': 1,
+ 'order_id': sale_order.id,
+ 'product_id': product.id,
+ 'product_uom_qty': cart['quantity'],
+ 'product_available_quantity': cart['available_quantity'],
+ 'price_unit': cart['price']['price'],
+ 'discount': discount_percent,
+ 'is_has_disc': is_has_disc
+ })
+ _logger.info(f"Created order line: {order_line.id}")
+
+ order_line.product_id_change()
+ order_line.weight = order_line.product_id.weight
+ order_line.onchange_vendor_id()
+ _logger.info(f"After onchanges - Price: {order_line.price_unit}, Disc: {order_line.discount}")
+
+ elif cart['cart_type'] == 'promotion':
+ promotions.append({
+ 'order_id': sale_order.id,
+ 'program_line_id': cart['id'],
+ 'quantity': cart['quantity']
+ })
+ _logger.info(f"Added promotion: {cart['id']}")
+
+ _logger.info("Processing promotions...")
+ sale_order._compute_etrts_date()
+ request.env['sale.order.promotion'].create(promotions)
+
+ if len(promotions) > 0:
+ _logger.info(f"Applying {len(promotions)} promotions")
+ sale_order.apply_promotion_program()
+ sale_order.add_free_product(promotions)
+
+ voucher_code = params['value']['voucher']
+ if voucher_code:
+ _logger.info(f"Processing voucher: {voucher_code}")
+ voucher = request.env['voucher'].search(
+ [('code', '=', voucher_code), ('apply_type', 'in', ['all', 'brand'])], limit=1)
+ voucher_shipping = request.env['voucher'].search(
+ [('code', '=', voucher_code), ('apply_type', 'in', ['shipping'])], limit=1)
+
+ if voucher and len(promotions) == 0:
+ _logger.info("Applying regular voucher")
+ sale_order.voucher_id = voucher.id
+ sale_order.apply_voucher()
+
+ if voucher_shipping and len(promotions) == 0:
+ _logger.info("Applying shipping voucher")
+ sale_order.voucher_shipping_id = voucher_shipping.id
+ sale_order.apply_voucher_shipping()
+
+ cart_ids = [x['cart_id'] for x in carts]
+ if sale_order._requires_approval_margin_leader():
+ sale_order.approval_status = 'pengajuan2'
+ _logger.info("Approval status set to 'pengajuan2'")
+ elif sale_order._requires_approval_margin_manager():
+ sale_order.approval_status = 'pengajuan1'
+ _logger.info("Approval status set to 'pengajuan1'")
+
+ sale_order._auto_set_shipping_from_website()
+ _logger.info("=== END CREATE PARTNER SALE ORDER ===")
+ return self.response({
+ 'id': sale_order.id,
+ 'name': sale_order.name
+ })
+
+ except Exception as e:
+ _logger.error(f"Error in create_partner_sale_order: {str(e)}", exc_info=True)
+ return self.response(code=500, description=str(e))
@http.route(PREFIX_PARTNER + 'sale-order/<id>/awb', auth='public', methods=['GET', 'OPTIONS'])
@controller.Controller.must_authorized(private=True, private_key='partner_id')
diff --git a/indoteknik_api/controllers/api_v1/voucher.py b/indoteknik_api/controllers/api_v1/voucher.py
index 9ffeeace..0338360b 100644
--- a/indoteknik_api/controllers/api_v1/voucher.py
+++ b/indoteknik_api/controllers/api_v1/voucher.py
@@ -74,7 +74,7 @@ class Voucher(controller.Controller):
partner_voucher_orders = []
for order in voucher.order_ids:
- if order.partner_id.id == user.partner_id.id:
+ if order.partner_id.id == user.partner_id.id and order.state != 'cancel' and (order.payment_status or order.payment_status is None):
partner_voucher_orders.append(order)
if voucher.limit_user > 0 and len(partner_voucher_orders) >= voucher.limit_user:
diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py
index 26b5df37..9f7df464 100644
--- a/indoteknik_custom/models/commision.py
+++ b/indoteknik_custom/models/commision.py
@@ -215,9 +215,8 @@ class CustomerCommision(models.Model):
grouped_so_number = fields.Char(string='Group SO Number', compute='_compute_grouped_numbers')
grouped_invoice_number = fields.Char(string='Group Invoice Number', compute='_compute_grouped_numbers')
- sales_id = fields.Many2one('res.users', string="Sales", tracking=True, default=lambda self: self.env.user,
- domain=lambda self: [
- ('groups_id', 'in', self.env.ref('sales_team.group_sale_salesman').id)])
+ sales_id = fields.Many2one('res.users', string="Sales", tracking=True, required=True,
+ domain=[('groups_id', 'in', [94]),('id', '!=', 15710)])
date_approved_sales = fields.Datetime(string="Date Approved Sales", tracking=True)
date_approved_marketing = fields.Datetime(string="Date Approved Marketing", tracking=True)
diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py
index 7977bdf7..91da0597 100644
--- a/indoteknik_custom/models/mrp_production.py
+++ b/indoteknik_custom/models/mrp_production.py
@@ -156,7 +156,7 @@ class MrpProduction(models.Model):
'order_id': new_po.id
}])
- new_po.button_confirm()
+ # new_po.button_confirm()
self.is_po = True
@@ -247,7 +247,7 @@ class CheckBomProduct(models.Model):
@api.constrains('production_id', 'product_id')
def _check_product_bom_validation(self):
for record in self:
- if record.production_id.sale_order.state not in ['sale', 'done']:
+ if record.production_id.sale_order and record.production_id.sale_order.state not in ['sale', 'done']:
raise UserError((
"SO harus diconfirm terlebih dahulu."
))
@@ -273,13 +273,13 @@ class CheckBomProduct(models.Model):
if existing_lines:
total_quantity = sum(existing_lines.mapped('quantity'))
- if total_quantity < total_qty_in_moves:
+ if total_quantity > total_qty_in_moves:
raise UserError((
"Quantity Product '%s' kurang dari quantity demand."
) % (record.product_id.display_name))
else:
# Check if the quantity exceeds the allowed total
- if record.quantity < total_qty_in_moves:
+ if record.quantity > total_qty_in_moves:
raise UserError((
"Quantity Product '%s' kurang dari quantity demand."
) % (record.product_id.display_name))
diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py
index 45134939..27aca0d1 100755
--- a/indoteknik_custom/models/purchase_order.py
+++ b/indoteknik_custom/models/purchase_order.py
@@ -92,6 +92,11 @@ class PurchaseOrder(models.Model):
is_cab_visible = fields.Boolean(string='Tampilkan Tombol CAB', compute='_compute_is_cab_visible')
+ reason_change_date_planned = fields.Selection([
+ ('delay', 'Delay By Vendor'),
+ ('urgent', 'Urgent Delivery'),
+ ], string='Reason Change Date Planned', tracking=True)
+
# picking_ids = fields.One2many('stock.picking', 'purchase_id', string='Pickings')
bu_related_count = fields.Integer(
@@ -100,9 +105,68 @@ class PurchaseOrder(models.Model):
)
manufacturing_id = fields.Many2one('mrp.production', string='Manufacturing Orders')
+ complete_bu_in_count = fields.Integer(
+ string="Complete BU In Count",
+ compute='_compute_complete_bu_in_count'
+ )
+
+ def _compute_complete_bu_in_count(self):
+ for order in self:
+ if order.state not in ['done', 'cancel']:
+ order.complete_bu_in_count = 1
+ else:
+ relevant_pickings = order.picking_ids.filtered(
+ lambda p: p.state != 'done'
+ and p.state != 'cancel'
+ and p.picking_type_code == 'incoming'
+ and p.origin == order.name
+ and p.name.startswith('BU/IN')
+ )
+ order.complete_bu_in_count = len(relevant_pickings)
+
def _has_vcm(self):
if self.id:
self.vcm_id = self.env['tukar.guling.po'].search([('origin', '=', self.name)], limit=1)
+
+ @api.depends('order_line.date_planned')
+ def _compute_date_planned(self):
+ """ date_planned = the earliest date_planned across all order lines. """
+ for order in self:
+ order.date_planned = False
+
+ @api.constrains('date_planned')
+ def constrains_date_planned(self):
+ for rec in self:
+ if not self.env.user.has_group('indoteknik_custom.group_role_purchasing'):
+ raise ValidationError("Hanya dapat diisi oleh Purchasing")
+
+ base_bu = self.env['stock.picking'].search([
+ ('name', 'ilike', 'BU/'),
+ ('origin', 'ilike', rec.name),
+ ('group_id', '=', rec.group_id.id),
+ ('state', 'not in', ['cancel','done'])
+ ])
+
+ for bu in base_bu:
+ bu.write({
+ 'scheduled_date': rec.date_planned,
+ 'reason_change_date_planned': rec.reason_change_date_planned
+ })
+
+ rec.sync_date_planned_to_so()
+
+ def sync_date_planned_to_so(self):
+ for line in self.order_sales_match_line:
+ other_sales_match = self.env['purchase.order.sales.match'].search([
+ # ('product_id', '=', line.product_id.id),
+ ('sale_id', '=', line.sale_id.id),
+ # ('sale_line_id', '=', line.sale_line_id.id)
+ ])
+
+ dates = [d for d in other_sales_match.mapped('purchase_order_id.date_planned') if d]
+ if dates:
+ date_planned = max(dates)
+ line.sale_id.write({'et_products': date_planned, 'reason_change_date_planned': line.purchase_order_id.reason_change_date_planned})
@api.depends('name')
def _compute_bu_related_count(self):
@@ -677,13 +741,6 @@ class PurchaseOrder(models.Model):
for order in self:
order.has_active_invoice = any(invoice.state != 'cancel' for invoice in order.invoice_ids)
- # def _compute_has_active_invoice(self):
- # for order in self:
- # related_invoices = order.invoice_ids.filtered(
- # lambda inv: inv.purchase_order_id.id == order.id and inv.move_type == 'in_invoice' and inv.state != 'cancel'
- # )
- # order.has_active_invoice = bool(related_invoices)
-
def add_product_to_pricelist(self):
i = 0
for line in self.order_line:
@@ -766,16 +823,16 @@ class PurchaseOrder(models.Model):
"""
purchase_pricelist.message_post(body=message, subtype_id=self.env.ref("mail.mt_note").id)
- def _compute_date_planned(self):
- for order in self:
- if order.date_approve:
- leadtime = order.partner_id.leadtime
- current_time = order.date_approve
- delta_time = current_time + timedelta(days=leadtime)
- delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S')
- order.date_planned = delta_time
- else:
- order.date_planned = False
+ # def _compute_date_planned(self):
+ # for order in self:
+ # if order.date_approve:
+ # leadtime = order.partner_id.leadtime
+ # current_time = order.date_approve
+ # delta_time = current_time + timedelta(days=leadtime)
+ # delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S')
+ # order.date_planned = delta_time
+ # else:
+ # order.date_planned = False
def action_create_invoice(self):
res = super(PurchaseOrder, self).action_create_invoice()
@@ -959,6 +1016,9 @@ class PurchaseOrder(models.Model):
if self.amount_untaxed >= 50000000 and not self.env.user.id == 21:
raise UserError("Hanya Rafly Hanggara yang bisa approve")
+
+ if not self.date_planned:
+ raise UserError("Receipt Date harus diisi")
if self.total_percent_margin < self.total_so_percent_margin:
self.env.user.notify_danger(
@@ -975,7 +1035,7 @@ class PurchaseOrder(models.Model):
# )
if not self.from_apo:
- if (not self.matches_so or not self.sale_order_id) and not self.env.user.is_purchasing_manager and not self.env.user.is_leader and not self.manufacturing_id:
+ if not self.matches_so and not self.env.user.is_purchasing_manager and not self.env.user.is_leader:
raise UserError("Tidak ada link dengan SO, harus di confirm oleh Purchasing Manager")
send_email = False
@@ -1010,10 +1070,10 @@ class PurchaseOrder(models.Model):
self.approve_by = self.env.user.id
# override date planned added with two days
- leadtime = self.partner_id.leadtime
- delta_time = current_time + timedelta(days=leadtime)
- delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S')
- self.date_planned = delta_time
+ # leadtime = self.partner_id.leadtime
+ # delta_time = current_time + timedelta(days=leadtime)
+ # delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S')
+ # self.date_planned = delta_time
self.date_deadline_ref_date_planned()
self.unlink_purchasing_job_state()
@@ -1391,6 +1451,20 @@ class PurchaseOrder(models.Model):
# Tambahkan pemanggilan method untuk handle pricelist system update
self._handle_pricelist_system_update(vals)
return res
+
+ def action_open_change_date_wizard(self):
+ self.ensure_one()
+ return {
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'change.date.planned.wizard',
+ 'view_mode': 'form',
+ 'target': 'new',
+ 'context': {
+ 'default_purchase_id': self.id,
+ 'default_new_date_planned': self.date_planned,
+ }
+ }
+
def _handle_pricelist_system_update(self, vals):
if 'order_line' in vals or any(key in vals for key in ['state', 'approval_status']):
@@ -1479,4 +1553,32 @@ class PurchaseOrderUnlockWizard(models.TransientModel):
order.approval_status_unlock = 'pengajuanFinance'
return {'type': 'ir.actions.act_window_close'}
+class ChangeDatePlannedWizard(models.TransientModel):
+ _name = 'change.date.planned.wizard'
+ _description = 'Change Date Planned Wizard'
+
+ purchase_id = fields.Many2one('purchase.order', string="Purchase Order", required=True)
+ new_date_planned = fields.Datetime(string="New Date Planned") # <- harus DTTM biar match
+ old_date_planned = fields.Datetime(string="Current Planned Date", related='purchase_id.date_planned', readonly=True)
+ reason = fields.Selection([
+ ('delay', 'Delay By Vendor'),
+ ('urgent', 'Urgent Delivery'),
+ ], string='Reason')
+ date_changed = fields.Boolean(string="Date Changed", compute="_compute_date_changed")
+
+ @api.depends('old_date_planned', 'new_date_planned')
+ def _compute_date_changed(self):
+ for rec in self:
+ rec.date_changed = (
+ rec.old_date_planned and rec.new_date_planned and
+ rec.old_date_planned != rec.new_date_planned
+ )
+
+ def confirm_change(self):
+ self.purchase_id.write({
+ 'date_planned': self.new_date_planned,
+ 'reason_change_date_planned': self.reason,
+ })
+
+
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index 7be0e8ff..4e36a9fb 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -350,7 +350,7 @@ class SaleOrder(models.Model):
date_unhold = fields.Datetime(string='Date Unhold', tracking=True, readonly=True, help='Waktu ketika SO di Unhold'
)
- et_products = fields.Datetime(string='ET Products', compute='_compute_et_products', help="Leadtime produk berdasarkan SLA vendor, tanpa logistik.")
+ et_products = fields.Datetime(string='ET Products', help="Leadtime produk berdasarkan SLA vendor, tanpa logistik.", tracking=True)
eta_date_reserved = fields.Datetime(
string="Date Reserved",
@@ -381,6 +381,11 @@ class SaleOrder(models.Model):
if self.id:
self.ccm_id = self.env['tukar.guling'].search([('origin', 'ilike', self.name)], limit=1)
+ reason_change_date_planned = fields.Selection([
+ ('delay', 'Delay By Vendor'),
+ ('urgent', 'Urgent Delivery'),
+ ], string='Reason Change Date Planned', tracking=True)
+
@api.depends('order_line.product_id', 'date_order')
def _compute_et_products(self):
jakarta = pytz.timezone("Asia/Jakarta")
diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py
index 5e9fc362..64b9f9bc 100644
--- a/indoteknik_custom/models/sale_order_line.py
+++ b/indoteknik_custom/models/sale_order_line.py
@@ -1,6 +1,10 @@
from odoo import fields, models, api, _
from odoo.exceptions import UserError
from datetime import datetime, timedelta
+import logging
+from odoo.tools.float_utils import float_compare
+
+_logger = logging.getLogger(__name__)
class SaleOrderLine(models.Model):
@@ -49,6 +53,9 @@ class SaleOrderLine(models.Model):
qty_free_bu = fields.Float(string='Free BU', compute='_get_qty_free_bandengan')
desc_updatable = fields.Boolean(string='desc boolean', default=True, compute='_get_desc_updatable')
+ is_has_disc = fields.Boolean('Flash Sale', default=False)
+
+
def _get_outgoing_incoming_moves(self):
outgoing_moves = self.env['stock.move']
incoming_moves = self.env['stock.move']
diff --git a/indoteknik_custom/models/shipment_group.py b/indoteknik_custom/models/shipment_group.py
index 4969c35a..7203b566 100644
--- a/indoteknik_custom/models/shipment_group.py
+++ b/indoteknik_custom/models/shipment_group.py
@@ -36,6 +36,8 @@ class ShipmentGroup(models.Model):
def create(self, vals):
vals['number'] = self.env['ir.sequence'].next_by_code('shipment.group') or '0'
result = super(ShipmentGroup, self).create(vals)
+ if result.shipment_line and result.shipment_line[0].partner_id:
+ result.partner_id = result.shipment_line[0].partner_id.id
return result
class ShipmentGroupLine(models.Model):
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index 3e152f10..825368de 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -303,6 +303,10 @@ class StockPicking(models.Model):
approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date')
last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim', copy=False)
update_date_doc_kirim_add = fields.Boolean(string='Update Tanggal Kirim Lewat ADD')
+ reason_change_date_planned = fields.Selection([
+ ('delay', 'Delay By Vendor'),
+ ('urgent', 'Urgent Delivery'),
+ ], string='Reason Change Date Planned', tracking=True)
def _get_kgx_awb_number(self):
"""Menggabungkan name dan origin untuk membuat AWB Number"""
@@ -1380,6 +1384,12 @@ class StockPicking(models.Model):
self.send_mail_bills()
if 'BU/PUT' in self.name:
self.automatic_reserve_product()
+
+ if self.tukar_guling_id:
+ self.tukar_guling_id.update_state()
+ elif self.tukar_guling_po_id:
+ self.tukar_guling_po_id.update_state()
+
return res
def automatic_reserve_product(self):
diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py
index 7253afb7..5411b17c 100644
--- a/indoteknik_custom/models/tukar_guling.py
+++ b/indoteknik_custom/models/tukar_guling.py
@@ -5,7 +5,8 @@ from datetime import datetime
_logger = logging.getLogger(__name__)
-#TODO
+
+# TODO
# 1. tracking status dokumen BU [X]
# 2. ganti nama dokumen
# 3. Tracking ketika create dokumen [X]
@@ -16,11 +17,11 @@ _logger = logging.getLogger(__name__)
class TukarGuling(models.Model):
_name = 'tukar.guling'
- _description = 'Tukar Guling'
+ _description = 'Pengajuan Retur SO'
_order = 'date desc, id desc'
_rec_name = 'name'
_inherit = ['mail.thread', 'mail.activity.mixin']
-
+
partner_id = fields.Many2one('res.partner', string='Customer', readonly=True)
origin = fields.Char(string='Origin SO')
if_so = fields.Boolean('Is SO', default=True)
@@ -62,6 +63,7 @@ class TukarGuling(models.Model):
('approval_sales', ' Approval Sales'),
('approval_finance', 'Approval Finance'),
('approval_logistic', 'Approval Logistic'),
+ ('approved', 'Waiting for Operations'),
('done', 'Done'),
('cancel', 'Canceled')
], default='draft', tracking=True, required=True)
@@ -98,7 +100,7 @@ class TukarGuling(models.Model):
@api.onchange('operations')
def _onchange_operations(self):
"""Auto-populate lines ketika operations dipilih"""
- if self.operations.picking_type_id.id not in [29,30]:
+ if self.operations.picking_type_id.id not in [29, 30]:
raise UserError("❌ Picking type harus BU/OUT atau BU/PICK")
for rec in self:
if rec.operations and rec.operations.picking_type_id.id == 30:
@@ -217,7 +219,6 @@ class TukarGuling(models.Model):
self.origin = False
-
def action_populate_lines(self):
"""Manual button untuk populate lines - sebagai alternatif"""
self.ensure_one()
@@ -257,7 +258,7 @@ class TukarGuling(models.Model):
def _check_product_lines(self):
"""Constraint: Product lines harus ada jika state bukan draft"""
for record in self:
- if record.state in ('approval_sales', 'approval_logistic', 'approval_finance',
+ if record.state in ('approval_sales', 'approval_logistic', 'approval_finance', 'approved',
'done') and not record.line_ids:
raise ValidationError("Product lines harus diisi sebelum submit atau approve!")
@@ -281,11 +282,11 @@ class TukarGuling(models.Model):
return True
- def _is_already_returned(self, picking):
- return self.env['stock.picking'].search_count([
- ('origin', '=', 'Return of %s' % picking.name),
- ('state', '!=', 'cancel')
- ]) > 0
+ # def _is_already_returned(self, picking):
+ # return self.env['stock.picking'].search_count([
+ # ('origin', '=', 'Return of %s' % picking.name),
+ # ('state', '!=', 'cancel')
+ # ]) > 0
@api.constrains('return_type', 'operations')
def _check_invoice_on_revisi_so(self):
@@ -345,7 +346,7 @@ class TukarGuling(models.Model):
def write(self, vals):
self.ensure_one()
- if self.operations.picking_type_id.id not in [29,30]:
+ if self.operations.picking_type_id.id not in [29, 30]:
raise UserError("❌ Picking type harus BU/OUT atau BU/PICK")
self._check_invoice_on_revisi_so()
operasi = self.operations.picking_type_id.id
@@ -376,24 +377,33 @@ class TukarGuling(models.Model):
# if self.state == 'done':
# raise UserError ("Tidak Boleh delete ketika sudahh done")
for record in self:
- if record.state == 'done':
+ if record.state == 'approved' or record.state == 'done':
raise UserError(
- "Tidak bisa hapus pengajuan jika sudah done, set ke draft terlebih dahulu jika ingin menghapus")
- ongoing_bu = self.picking_ids.filtered(lambda p: p.state != 'done')
+ "Tidak bisa hapus pengajuan jika sudah Approved, set ke draft terlebih dahulu jika ingin menghapus")
+ ongoing_bu = self.picking_ids.filtered(lambda p: p.state != 'approved')
for picking in ongoing_bu:
picking.action_cancel()
return super(TukarGuling, self).unlink()
def action_view_picking(self):
self.ensure_one()
- action = self.env.ref('stock.action_picking_tree_all').read()[0]
- pickings = self.picking_ids
- if len(pickings) > 1:
- action['domain'] = [('id', 'in', pickings.ids)]
- elif pickings:
- action['views'] = [(self.env.ref('stock.view_picking_form').id, 'form')]
- action['res_id'] = pickings.id
- return action
+
+ # picking_origin = f"Return of {self.operations.name}"
+ returs = self.env['stock.picking'].search([
+ ('tukar_guling_id', '=', self.id),
+ ])
+
+ if not returs:
+ raise UserError("Doc Retrun Not Found")
+
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': 'Delivery Pengajuan Retur SO',
+ 'res_model': 'stock.picking',
+ 'view_mode': 'tree,form',
+ 'domain': [('id', 'in', returs.ids)],
+ 'target': 'current',
+ }
def action_draft(self):
"""Reset to draft state"""
@@ -434,8 +444,8 @@ class TukarGuling(models.Model):
linked_bu_out = picking.linked_manual_bu_out
if linked_bu_out and linked_bu_out.state == 'done':
raise UserError("❌ Tidak bisa retur BU/PICK karena BU/OUT suda Done!")
- if self._is_already_returned(self.operations):
- raise UserError("BU ini sudah pernah diretur oleh dokumen lain.")
+ # if self._is_already_returned(self.operations):
+ # raise UserError("BU ini sudah pernah diretur oleh dokumen lain.")
if self.operations.picking_type_id.id == 29:
for line in self.line_ids:
@@ -452,6 +462,47 @@ class TukarGuling(models.Model):
raise UserError("Submit hanya bisa dilakukan dari Draft.")
self.state = 'approval_sales'
+ def update_state(self):
+ # OUT tukar guling
+ if self.operations.picking_type_id.id == 29 and self.return_type == 'tukar_guling':
+ total_out = self.env['stock.picking'].search_count([
+ ('tukar_guling_id', '=', self.id),
+ ('picking_type_id', '=', 29),
+ ])
+ done_out = self.env['stock.picking'].search_count([
+ ('tukar_guling_id', '=', self.id),
+ ('picking_type_id', '=', 29),
+ ('state', '=', 'done'),
+ ])
+ if self.state == 'approved' and total_out > 0 and done_out == total_out:
+ self.state = 'done'
+
+ # OUT revisi SO
+ elif self.operations.picking_type_id.id == 29 and self.return_type == 'revisi_so':
+ total_ort = self.env['stock.picking'].search_count([
+ ('tukar_guling_id', '=', self.id),
+ ('picking_type_id', '=', 74),
+ ])
+ done_ort = self.env['stock.picking'].search_count([
+ ('tukar_guling_id', '=', self.id),
+ ('picking_type_id', '=', 74),
+ ('state', '=', 'done'),
+ ])
+ if self.state == 'approved' and total_ort > 0 and done_ort == total_ort:
+ self.state = 'done'
+
+ # PICK revisi SO
+ elif self.operations.picking_type_id.id == 30 and self.return_type == 'revisi_so':
+ done_ort = self.env['stock.picking'].search([
+ ('tukar_guling_id', '=', self.id),
+ ('picking_type_id', '=', 74),
+ ('state', '=', 'done'),
+ ])
+ if self.state == 'approved' and done_ort:
+ self.state = 'done'
+ else:
+ raise UserError("Tidak bisa menentukan jenis retur.")
+
def action_approve(self):
self.ensure_one()
self._validate_product_lines()
@@ -501,7 +552,7 @@ class TukarGuling(models.Model):
elif rec.state == 'approval_logistic':
if not rec.env.user.has_group('indoteknik_custom.group_role_logistic'):
raise UserError("Hanya Logistic Manager yang boleh approve tahap ini.")
- rec.state = 'done'
+ rec.state = 'approved'
rec._create_pickings()
rec.date_logistic = now
@@ -597,7 +648,8 @@ class TukarGuling(models.Model):
### ======== ORT dari BU/PICK =========
ort_pickings = []
is_retur_from_bu_pick = record.operations.picking_type_id.id == 30
- picks_to_return = [record.operations] if is_retur_from_bu_pick else mapping_koli.mapped('pick_id') or line.product_uom_qty
+ picks_to_return = [record.operations] if is_retur_from_bu_pick else mapping_koli.mapped(
+ 'pick_id') or line.product_uom_qty
for pick in picks_to_return:
ort_return_lines = []
@@ -613,7 +665,8 @@ class TukarGuling(models.Model):
'quantity': line.product_uom_qty,
'move_id': move.id,
}))
- _logger.info(f"📟 ORT (BU/PICK langsung) | {pick.name} | {line.product_id.display_name} | qty={line.product_uom_qty}")
+ _logger.info(
+ f"📟 ORT (BU/PICK langsung) | {pick.name} | {line.product_id.display_name} | qty={line.product_uom_qty}")
else:
# Ambil dari mapping koli
for mk in mapping_koli.filtered(lambda m: m.pick_id == pick):
@@ -626,7 +679,8 @@ class TukarGuling(models.Model):
'quantity': mk.qty_return,
'move_id': move.id,
}))
- _logger.info(f"📟 ORT (mapping koli) | {pick.name} | {mk.product_id.display_name} | qty={mk.qty_return}")
+ _logger.info(
+ f"📟 ORT (mapping koli) | {pick.name} | {mk.product_id.display_name} | qty={mk.qty_return}")
if ort_return_lines:
ort_wizard = self.env['stock.return.picking'].with_context({
@@ -808,18 +862,17 @@ class StockPicking(models.Model):
message = _(
"📦 <b>%s</b> Validated by <b>%s</b> Status Changed <b>%s</b> at <b>%s</b>."
) % (
- picking.name,
- # picking.picking_type_id.name,
- picking.env.user.name,
- picking.state,
- fields.Datetime.now().strftime("%d/%m/%Y %H:%M")
- )
+ picking.name,
+ # picking.picking_type_id.name,
+ picking.env.user.name,
+ picking.state,
+ fields.Datetime.now().strftime("%d/%m/%Y %H:%M")
+ )
picking.tukar_guling_id.message_post(body=message)
return res
-
class TukarGulingMappingKoli(models.Model):
_name = 'tukar.guling.mapping.koli'
_description = 'Mapping Koli di Tukar Guling'
@@ -830,6 +883,7 @@ class TukarGulingMappingKoli(models.Model):
qty_done = fields.Float(string='Qty Done BU PICK')
qty_return = fields.Float(string='Qty diretur')
sequence = fields.Integer(string='Sequence', default=10)
+
@api.constrains('qty_return')
def _check_qty_return_editable(self):
for rec in self:
@@ -840,4 +894,4 @@ class TukarGulingMappingKoli(models.Model):
for rec in self:
if rec.tukar_guling_id and rec.tukar_guling_id.state not in ['draft', 'cancel']:
raise UserError("Tidak bisa menghapus Mapping Koli karena status Tukar Guling bukan Draft atau Cancel.")
- return super(TukarGulingMappingKoli, self).unlink() \ No newline at end of file
+ return super(TukarGulingMappingKoli, self).unlink()
diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py
index 7c9680f8..23ca1923 100644
--- a/indoteknik_custom/models/tukar_guling_po.py
+++ b/indoteknik_custom/models/tukar_guling_po.py
@@ -10,7 +10,7 @@ _logger = logging.getLogger(__name__)
class TukarGulingPO(models.Model):
_name = 'tukar.guling.po'
- _description = 'Tukar Guling PO'
+ _description = 'Pengajuan Retur PO'
_inherit = ['mail.thread', 'mail.activity.mixin']
vendor_id = fields.Many2one('res.partner', string='Vendor Name', readonly=True)
@@ -49,6 +49,7 @@ class TukarGulingPO(models.Model):
('approval_purchase', 'Approval Purchasing'),
('approval_finance', 'Approval Finance'),
('approval_logistic', 'Approval Logistic'),
+ ('approved', 'Waiting for Operations'),
('done', 'Done'),
('cancel', 'Cancel'),
], string='Status', default='draft', tracking=3)
@@ -245,12 +246,12 @@ class TukarGulingPO(models.Model):
return True
- def _is_already_returned(self, picking):
- return self.env['stock.picking'].search_count([
- ('origin', '=', 'Return of %s' % picking.name),
- # ('returned_from_id', '=', picking.id),
- ('state', 'not in', ['cancel', 'draft']),
- ]) > 0
+ # def _is_already_returned(self, picking):
+ # return self.env['stock.picking'].search_count([
+ # ('origin', '=', 'Return of %s' % picking.name),
+ # # ('returned_from_id', '=', picking.id),
+ # ('state', 'not in', ['cancel', 'draft']),
+ # ]) > 0
def copy(self, default=None):
if default is None:
@@ -311,7 +312,7 @@ class TukarGulingPO(models.Model):
def unlink(self):
for record in self:
- if record.state == 'done':
+ if record.state == 'done' or record.state == 'approved':
raise UserError("Tidak bisa hapus pengajuan jika sudah done, set ke draft terlebih dahulu")
ongoing_bu = self.po_picking_ids.filtered(lambda p: p.state != 'done')
for picking in ongoing_bu:
@@ -320,14 +321,23 @@ class TukarGulingPO(models.Model):
def action_view_picking(self):
self.ensure_one()
- action = self.env.ref('stock.action_picking_tree_all').read()[0]
- pickings = self.po_picking_ids
- if len(pickings) > 1:
- action['domain'] = [('id', 'in', pickings.ids)]
- elif pickings:
- action['views'] = [(self.env.ref('stock.view_picking_form').id, 'form')]
- action['res_id'] = pickings.id
- return action
+
+ # picking_origin = f"Return of {self.operations.name}"
+ returs = self.env['stock.picking'].search([
+ ('tukar_guling_po_id', '=', self.id),
+ ])
+
+ if not returs:
+ raise UserError("Doc Retrun Not Found")
+
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': 'Delivery Pengajuan Retur PO',
+ 'res_model': 'stock.picking',
+ 'view_mode': 'tree,form',
+ 'domain': [('id', 'in', returs.ids)],
+ 'target': 'current',
+ }
def action_draft(self):
"""Reset to draft state"""
@@ -365,8 +375,8 @@ class TukarGulingPO(models.Model):
if pick_id not in [75, 28]:
raise UserError("❌ Tidak bisa retur bukan BU/INPUT atau BU/PUT!")
- if self._is_already_returned(self.operations):
- raise UserError("BU ini sudah pernah diretur oleh dokumen lain.")
+ # if self._is_already_returned(self.operations):
+ # raise UserError("BU ini sudah pernah diretur oleh dokumen lain.")
if self.state != 'draft':
raise UserError("Submit hanya bisa dilakukan dari Draft.")
@@ -403,12 +413,50 @@ class TukarGulingPO(models.Model):
elif rec.state == 'approval_logistic':
if not rec.env.user.has_group('indoteknik_custom.group_role_logistic'):
raise UserError("Hanya Logistic Manager yang boleh approve tahap ini.")
- rec.state = 'done'
+ rec.state = 'approved'
rec._create_pickings()
rec.date_logistic = now
else:
raise UserError("Status ini tidak bisa di-approve.")
+ def update_stae(self):
+ # bu input rev po
+ if self.operations.picking_type_id.id == 28 and self.return_type == 'revisi_po':
+ prt = self.env['stock.picking'].search([
+ ('tukar_guling_po_id', '=', self.id),
+ ('state', '=', 'done'),
+ ('picking_type_id.id', '=', 76)
+ ])
+ if self.state == 'aproved' and prt:
+ self.state = 'done'
+ # bu put rev po
+ elif self.operations.picking_type_id.id == 75 and self.return_type == 'revisi_po':
+ total_prt = self.env['stock.picking'].search_count([
+ ('tukar_guling_po_id', '=', self.id),
+ ('picking_type_id.id', '=', 76)
+ ])
+ prt = self.env['stock.picking'].search_count([
+ ('tukar_guling_po_id', '=', self.id),
+ ('state', '=', 'done'),
+ ('picking_type_id.id', '=', 76)
+ ])
+ if self.state == 'aproved' and total_prt > 0 and prt == total_prt:
+ self.state = 'done'
+ # bu put tukar guling
+ elif self.operations.picking_type_id.id == 75 and self.return_type == 'tukar_guling':
+ total_put = self.env['stock.picking'].search_count([
+ ('tukar_guling_po_id', '=', self.id),
+ ('picking_type_id.id', '=', 75)
+ ])
+ put = self.env['stock.picking'].search_count([
+ ('tukar_guling_po_id', '=', self.id),
+ ('state', '=', 'done'),
+ ('picking_type_id.id', '=', 75)
+ ])
+ if self.state == 'aproved' and total_put > 0 and put == total_put:
+ self.state = 'done'
+
+
def action_cancel(self):
self.ensure_one()
# if self.state == 'done':
@@ -629,7 +677,7 @@ class TukarGulingLinePO(models.Model):
@api.constrains('product_uom_qty')
def _check_qty_change_allowed(self):
for rec in self:
- if rec.tukar_guling_id and rec.tukar_guling_po_id.state not in ['draft', 'cancel']:
+ if rec.tukar_guling_po_id and rec.tukar_guling_po_id.state not in ['draft', 'cancel']:
raise ValidationError("Tidak bisa mengubah Quantity karena status dokumen bukan Draft atau Cancel.")
def unlink(self):
diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv
index 0ac3e86c..9b43bf2a 100755
--- a/indoteknik_custom/security/ir.model.access.csv
+++ b/indoteknik_custom/security/ir.model.access.csv
@@ -161,6 +161,7 @@ access_konfirm_koli,access.konfirm.koli,model_konfirm_koli,,1,1,1,1
access_stock_immediate_transfer,access.stock.immediate.transfer,model_stock_immediate_transfer,,1,1,1,1
access_coretax_faktur,access.coretax.faktur,model_coretax_faktur,,1,1,1,1
access_purchase_order_unlock_wizard,access.purchase.order.unlock.wizard,model_purchase_order_unlock_wizard,,1,1,1,1
+access_change_date_planned_wizard,access.change.date.planned.wizard,model_change_date_planned_wizard,,1,1,1,1
access_sales_order_koli,access.sales.order.koli,model_sales_order_koli,,1,1,1,1
access_stock_backorder_confirmation,access.stock.backorder.confirmation,model_stock_backorder_confirmation,,1,1,1,1
access_warning_modal_wizard,access.warning.modal.wizard,model_warning_modal_wizard,,1,1,1,1
@@ -183,8 +184,7 @@ access_production_purchase_match,access.production.purchase.match,model_producti
access_image_carousel,access.image.carousel,model_image_carousel,,1,1,1,1
access_v_sale_notin_matchpo,access.v.sale.notin.matchpo,model_v_sale_notin_matchpo,,1,1,1,1
access_approval_payment_term,access.approval.payment.term,model_approval_payment_term,,1,1,1,1
-access_refund_sale_order,access.refund.sale.order,model_refund_sale_order,base.group_user,1,1,1,1
-access_refund_sale_order_line,access.refund.sale.order.line,model_refund_sale_order_line,base.group_user,1,1,1,1
+
access_purchasing_job_seen,purchasing.job.seen,model_purchasing_job_seen,,1,1,1,1
access_tukar_guling_all_users,tukar.guling.all.users,model_tukar_guling,base.group_user,1,1,1,1
diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml
index ff223125..fedcb4f9 100755
--- a/indoteknik_custom/views/purchase_order.xml
+++ b/indoteknik_custom/views/purchase_order.xml
@@ -75,11 +75,13 @@
</field>
<field name="partner_id" position="after">
<field name="purchase_order_count"/>
+ <field name="complete_bu_in_count" invisible="1"/>
</field>
<field name="incoterm_id" position="after">
<field name="amount_total_without_service"/>
<field name="delivery_amt"/>
<field name="approve_by"/>
+ <field name="reason_change_date_planned"/>
</field>
<field name="currency_id" position="after">
<field name="summary_qty_po"/>
@@ -106,9 +108,16 @@
<field name="product_id" position="attributes">
<attribute name="options">{'no_create': True}</attribute>
</field>
- <field name="date_planned" position="attributes">
- <attribute name="invisible">1</attribute>
- </field>
+ <xpath expr="//field[@name='date_planned']" position="replace">
+ <field name="date_planned" readonly="1"/>
+ </xpath>
+ <xpath expr="//field[@name='date_planned']" position="after">
+ <button name="action_open_change_date_wizard"
+ type="object"
+ string="Change Receipt Date"
+ class="btn-primary"
+ attrs="{'invisible': ['|', ('state', '=', 'cancel'), ('complete_bu_in_count', '=', 0)]}"/>
+ </xpath>
<field name="product_qty" position="before">
<field name="is_edit_product_qty" readonly="1" optional="hide"/>
<field name="qty_onhand" readonly="1" optional="hide"/>
@@ -225,6 +234,34 @@
</data>
<data>
+ <record id="view_change_date_planned_wizard_form" model="ir.ui.view">
+ <field name="name">change.date.planned.wizard.form</field>
+ <field name="model">change.date.planned.wizard</field>
+ <field name="arch" type="xml">
+ <form string="Change Date Planned">
+ <group>
+ <field name="purchase_id" readonly="1"/>
+ <field name="old_date_planned" readonly="1"/>
+ <field name="date_changed" invisible="1"/>
+ <field name="new_date_planned"/>
+ <field name="reason"
+ attrs="{
+ 'invisible': ['|', ('old_date_planned', '=', False), ('date_changed', '=', False)],
+ 'required': [('date_changed', '=', True)]
+ }"/>
+ </group>
+
+ <footer>
+ <button name="confirm_change" type="object" string="Confirm" class="btn-primary"/>
+ <button string="Cancel" class="btn-secondary" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+ </data>
+
+
+ <data>
<record id="rfq_order_tree_view_inherit" model="ir.ui.view">
<field name="name">Purchase</field>
<field name="model">purchase.order</field>
diff --git a/indoteknik_custom/views/refund_sale_order.xml b/indoteknik_custom/views/refund_sale_order.xml
deleted file mode 100644
index 4f791722..00000000
--- a/indoteknik_custom/views/refund_sale_order.xml
+++ /dev/null
@@ -1,199 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<odoo>
- <!-- Tree View -->
- <record id="view_refund_sale_order_tree" model="ir.ui.view">
- <field name="name">refund.sale.order.tree</field>
- <field name="model">refund.sale.order</field>
- <field name="arch" type="xml">
- <tree string="Refund Sales Orders">
- <field name="name" readonly="1"/>
- <field name="created_date" readonly="1"/>
- <field name="partner_id" readonly="1"/>
- <field name="sale_order_ids" widget="many2many_tags" readonly="1"/>
- <field name="uang_masuk" readonly="1"/>
- <field name="ongkir" readonly="1"/>
- <field name="total_invoice" readonly="1"/>
- <field name="amount_refund" readonly="1"/>
- <field name="status"
- decoration-info="status == 'draft'"
- decoration-danger="status == 'reject'"
- decoration-success="status == 'refund'"
- decoration-warning="status == 'pengajuan1' or status == 'pengajuan2' or status == 'pengajuan3'"
- widget="badge"
- readonly="1"/>
- <field name="status_payment"
- decoration-info="status_payment == 'pending'"
- decoration-danger="status_payment == 'reject'"
- decoration-success="status_payment == 'done'"
- widget="badge"
- readonly="1"/>
- <field name="refund_date" readonly="1"/>
- <field name="amount_refund_text" readonly="1" optional="hide"/>
- <field name="invoice_ids" readonly="1" optional="hide"/>
- <field name="refund_type" readonly="1" optional="hide"/>
- <field name="user_ids" readonly="1" optional="hide"/>
- </tree>
- </field>
- </record>
-
- <!-- Form View -->
- <record id="view_refund_sale_order_form" model="ir.ui.view">
- <field name="name">refund.sale.order.form</field>
- <field name="model">refund.sale.order</field>
- <field name="arch" type="xml">
- <form string="Refund Sales Order">
- <header>
- <button name="action_ask_approval"
- type="object"
- string="Ask Approval"
- attrs="{'invisible': [('status', '!=', 'draft')]}"/>
-
- <button name="action_approve_flow"
- type="object"
- string="Approve"
- class="oe_highlight"
- attrs="{'invisible': [('status', 'in', ['refund', 'reject', 'draft'])]}"/>
- <button name="action_trigger_cancel"
- type="object"
- string="Cancel"
- attrs="{'invisible': ['|', ('status_payment', '!=', 'pending'), ('status', '=', 'reject')]}" />
- <button name="action_confirm_refund"
- type="object"
- string="Confirm Refund"
- class="btn-primary"
- attrs="{'invisible': ['|', ('status', 'not in', ['pengajuan3','refund']), ('status_payment', '!=', 'pending')]}"/>
- <button name="action_create_journal_refund"
- string="Journal Refund"
- type="object"
- class="oe_highlight"
- attrs="{'invisible': ['|', ('status', 'not in', ['pengajuan3','refund']), ('journal_refund_state', '=', 'posted')]}"/>
-
- <field name="status"
- widget="statusbar"
- statusbar_visible="draft,pengajuan1,pengajuan2,pengajuan3,reject"
- attrs="{'invisible': [('status', '!=', 'reject')]}" />
-
- <field name="status"
- widget="statusbar"
- statusbar_visible="draft,pengajuan1,pengajuan2,pengajuan3,refund"
- attrs="{'invisible': [('status', '=', 'reject')]}" />
- </header>
- <sheet>
- <div class="oe_button_box" name="button_box">
- <button name="action_open_journal_refund"
- type="object"
- class="oe_stat_button"
- icon="fa-book"
- width="250px"
- attrs="{'invisible': ['|', ('journal_refund_move_id', '=', False), ('journal_refund_state', '!=', 'posted')]}">
- <field name="journal_refund_move_id" string="Journal Refund" widget="statinfo"/>
- </button>
- </div>
- <widget name="web_ribbon"
- title="PAID"
- bg_color="bg-success"
- attrs="{'invisible': [('status_payment', '!=', 'done')]}"/>
-
- <widget name="web_ribbon"
- title="CANCEL"
- bg_color="bg-danger"
- attrs="{'invisible': [('status_payment', '!=', 'reject')]}"/>
- <h1>
- <field name="name" readonly="1"/>
- </h1>
- <group col="2">
- <group>
- <field name="is_locked" invisible="1"/>
- <field name="status_payment" invisible="1"/>
- <field name="journal_refund_state" invisible="1"/>
-
- <field name="partner_id" attrs="{'readonly': [('is_locked', '=', True)]}"/>
- <field name="sale_order_ids" widget="many2many_tags" attrs="{'readonly': [('is_locked', '=', True)]}"/>
- <field name="invoice_ids" widget="many2many_tags" readonly="1"/>
- <field name="invoice_names" widget="html" readonly="1"/>
- <field name="so_names" widget="html" readonly="1"/>
- <field name="advance_move_names" widget="html" readonly="1"/>
- <field name="refund_type" attrs="{'readonly': [('is_locked', '=', True)]}"/>
- <field name="note_refund" attrs="{'readonly': [('is_locked', '=', True)]}"/>
- </group>
- <group>
- <field name="uang_masuk" attrs="{'readonly': [('is_locked', '=', True)]}"/>
- <field name="total_invoice" attrs="{'readonly': [('is_locked', '=', True)]}"/>
- <field name="ongkir" attrs="{'readonly': [('is_locked', '=', True)]}"/>
- <field name="amount_refund" attrs="{'readonly': [('is_locked', '=', True)]}"/>
- <field name="amount_refund_text" readonly="1"/>
- <field name="uang_masuk_type" required="1" attrs="{'readonly': [('is_locked', '=', True)]}"/>
- <field name="bukti_uang_masuk_image" widget="image"
- attrs="{'invisible': [('uang_masuk_type', '=', 'pdf')], 'readonly': [('is_locked', '=', True)]}"/>
- <field name="bukti_uang_masuk_pdf" widget="pdf_viewer"
- attrs="{'invisible': [('uang_masuk_type', '=', 'image')], 'readonly': [('is_locked', '=', True)]}"/>
- </group>
- </group>
-
- <notebook>
- <page string="Produk Line">
- <field name="line_ids" attrs="{'readonly': [('is_locked', '=', True)]}">
- <tree editable="bottom">
- <field name="product_id"/>
- <field name="quantity"/>
- <field name="reason"/>
- </tree>
- </field>
- </page>
-
- <page string="Other Info">
- <group col="2">
- <group>
- <field name="user_ids" widget="many2many_tags" readonly="1"/>
- <field name="created_date" readonly="1"/>
- <field name="refund_date" attrs="{'readonly': [('status', 'not in', ['pengajuan3','refund'])]}"/>
- </group>
- <group>
- <field name="bank" attrs="{'readonly': [('is_locked', '=', True)]}"/>
- <field name="account_name" attrs="{'readonly': [('is_locked', '=', True)]}"/>
- <field name="account_no" attrs="{'readonly': [('is_locked', '=', True)]}"/>
- </group>
- </group>
- </page>
-
- <page string="Finance Note">
- <group col="2">
- <group>
- <field name="finance_note" attrs="{'readonly': [('is_locked', '=', True)]}"/>
- </group>
- <group>
- <field name="bukti_refund_type" reqiured="1" attrs="{'readonly': [('is_locked', '=', True)]}"/>
- <field name="bukti_transfer_refund_pdf" widget="pdf_viewer" attrs="{'invisible': [('bukti_refund_type', '=', 'image')]}"/>
- <field name="bukti_transfer_refund_image" widget="image" attrs="{'invisible': [('bukti_refund_type', '=', 'pdf')]}"/>
- </group>
- </group>
- </page>
-
- <page string="Cancel Reason" attrs="{'invisible': [('status', '=', 'refund')]}">
- <group>
- <field name="reason_reject"/>
- </group>
- </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>
- <!-- Action -->
- <record id="action_refund_sale_order" model="ir.actions.act_window">
- <field name="name">Refund Sales Order</field>
- <field name="res_model">refund.sale.order</field>
- <field name="view_mode">tree,form</field>
- </record>
-
- <!-- Menu -->
- <menuitem id="menu_refund_sale_order"
- name="Refund"
- parent="sale.sale_order_menu"
- sequence="10"
- action="action_refund_sale_order"/>
-</odoo>
diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml
index c1f1fe61..e8f41ca3 100755
--- a/indoteknik_custom/views/sale_order.xml
+++ b/indoteknik_custom/views/sale_order.xml
@@ -172,6 +172,7 @@
<xpath expr="//page[@name='other_information']/group/group[@name='sale_reporting']" position="after">
<group string="ETA">
<field name="et_products"/>
+ <field name="reason_change_date_planned" readonly="1"/>
<field name="eta_date_reserved"/>
<field name="expected_ready_to_ship"/>
<field name="eta_date_start"/>
@@ -290,6 +291,7 @@
<field name="note_procurement" optional="hide"/>
<field name="vendor_subtotal" optional="hide"/>
<field name="weight" optional="hide"/>
+ <field name="is_has_disc" string="Flash Sale Item?" readonly="1" 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>
diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml
index f9200dfa..b3f0ce9f 100644
--- a/indoteknik_custom/views/stock_picking.xml
+++ b/indoteknik_custom/views/stock_picking.xml
@@ -129,6 +129,9 @@
<field name="date_done" position="after">
<field name="arrival_time"/>
</field>
+ <field name="scheduled_date" position="attributes">
+ <attribute name="readonly">1</attribute>
+ </field>
<field name="origin" position="after">
<!-- <field name="show_state_approve_md" invisible="1" optional="hide"/>-->
<field name="state_approve_md" widget="badge"/>
@@ -165,6 +168,7 @@
<field name="approval_receipt_status"/>
<field name="approval_return_status"/>
<field name="so_lama"/>
+ <field name="reason_change_date_planned" readonly="1"/>
</field>
<field name="product_id" position="before">
<field name="line_no" attrs="{'readonly': 1}" optional="hide"/>
diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml
index a79f8b55..c23995d3 100644
--- a/indoteknik_custom/views/tukar_guling.xml
+++ b/indoteknik_custom/views/tukar_guling.xml
@@ -23,16 +23,18 @@
<field name="arch" type="xml">
<tree create="1" delete="1" default_order="create_date desc">
<field name="name"/>
- <field name="date"/>
+ <field name="partner_id" string="Customer"/>
<field name="origin" string="SO Number"/>
<field name="operations" string="Operations"/>
<field name="return_type" string="Return Type"/>
<field name="state" widget="badge"
decoration-info="state in ('draft', 'approval_sales', 'approval_finance','approval_logistic')"
+ decoration-warning="state == 'approved'"
decoration-success="state == 'done'"
decoration-muted="state == 'cancel'"
/>
<field name="ba_num" string="Nomor BA"/>
+ <field name="date"/>
<field name="date_logistic" string="Approved Date"/>
</tree>
</field>
@@ -57,7 +59,7 @@
class="btn-secondary"
attrs="{'invisible': [('state', '!=', 'cancel')]}"/>
<field name="state" widget="statusbar" readonly="1"
- statusbar_visible="draft,approval_sales,approval_logistic,approval_finance,done"/>
+ statusbar_visible="draft,approval_sales,approval_logistic,approval_finance,approved,done"/>
</header>
<sheet>
<div class="oe_button_box">
@@ -65,7 +67,7 @@
type="object"
class="oe_stat_button"
icon="fa-truck"
- attrs="{'invisible': [('picking_ids', '=', False), ('state', 'in', ['draft', 'approval_sales', 'approval_logistic', 'approval_finance'])]}">
+ attrs="{'invisible': [('picking_ids', '=', False), ('state', 'in', ['draft', 'approval_sales', 'approval_logistic', 'approval_finance', 'approved', 'done', 'cancel'])]}">
<field name="picking_ids" widget="statinfo" string="Delivery"/>
</button>
</div>
diff --git a/indoteknik_custom/views/tukar_guling_po.xml b/indoteknik_custom/views/tukar_guling_po.xml
index 77feb05f..accf7dbc 100644
--- a/indoteknik_custom/views/tukar_guling_po.xml
+++ b/indoteknik_custom/views/tukar_guling_po.xml
@@ -23,18 +23,19 @@
<field name="arch" type="xml">
<tree create="1" delete="1" default_order="create_date desc">
<field name="name"/>
- <field name="date"/>
- <field name="origin" string="PO Number"/>
+ <field name="vendor_id" string="Customer"/>
+ <field name="origin" string="SO Number"/>
<field name="operations" string="Operations"/>
<field name="return_type" string="Return Type"/>
- <field name="ba_num" string="Nomor BA"/>
- <field name="return_type" string="Return Type"/>
<field name="state" widget="badge"
decoration-info="state in ('draft', 'approval_purchase', 'approval_finance','approval_logistic')"
+ decoration-warning="state == 'approved'"
decoration-success="state == 'done'"
decoration-muted="state == 'cancel'"
/>
- <field name="date_logistic" string="Approved Date"/>
+ <field name="ba_num" string="Nomor BA"/>
+ <field name="date"/>
+ <field name="date_logistic" string="Approved Date"/>
</tree>
</field>
</record>
@@ -60,7 +61,7 @@
attrs="{'invisible': [('state', '!=', 'cancel')]}"
confirm="Are you sure you want to reset this record to draft?"/>
<field name="state" widget="statusbar" readonly="1"
- statusbar_visible="draft,approval_purchase,approval_logistic,approval_finance,done"/>
+ statusbar_visible="draft,approval_purchase,approval_logistic,approval_finance,approved,done"/>
</header>
<sheet>
<div class="oe_button_box">