summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIndoteknik . <it@fixcomart.co.id>2025-07-05 15:00:30 +0700
committerIndoteknik . <it@fixcomart.co.id>2025-07-05 15:00:30 +0700
commit2b7d3e4f575c881b73f18b242dbf65e9bbf4c5ab (patch)
treefe9d5aca45b030cfedcc8875c02bafcb277d8f3f
parent20f206f3d9b798fee50a06d4a462cf256a71d58e (diff)
parent5b1b45d46e34c6724572b9b3182813e0bfdea0a3 (diff)
(andri) fix conflict
-rw-r--r--indoteknik_api/controllers/api_v1/stock_picking.py15
-rwxr-xr-xindoteknik_custom/__manifest__.py1
-rwxr-xr-xindoteknik_custom/models/__init__.py1
-rw-r--r--indoteknik_custom/models/account_move.py34
-rw-r--r--indoteknik_custom/models/approval_payment_term.py82
-rw-r--r--indoteknik_custom/models/automatic_purchase.py2
-rw-r--r--indoteknik_custom/models/commision.py68
-rw-r--r--indoteknik_custom/models/coretax_fatur.py90
-rw-r--r--indoteknik_custom/models/mrp_production.py5
-rw-r--r--indoteknik_custom/models/patch/http_override.py1
-rwxr-xr-xindoteknik_custom/models/purchase_order.py61
-rw-r--r--indoteknik_custom/models/purchase_order_sales_match.py14
-rw-r--r--indoteknik_custom/models/res_partner.py22
-rwxr-xr-xindoteknik_custom/models/sale_order.py168
-rw-r--r--indoteknik_custom/models/sale_orders_multi_update.py7
-rw-r--r--indoteknik_custom/models/stock_picking.py14
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv1
-rw-r--r--indoteknik_custom/views/account_move.xml4
-rw-r--r--indoteknik_custom/views/approval_payment_term.xml74
-rw-r--r--indoteknik_custom/views/customer_commision.xml9
-rw-r--r--indoteknik_custom/views/ir_sequence.xml42
-rw-r--r--indoteknik_custom/views/mrp_production.xml2
-rwxr-xr-xindoteknik_custom/views/purchase_order.xml4
-rw-r--r--indoteknik_custom/views/res_partner.xml12
24 files changed, 588 insertions, 145 deletions
diff --git a/indoteknik_api/controllers/api_v1/stock_picking.py b/indoteknik_api/controllers/api_v1/stock_picking.py
index da18e16a..0525adc3 100644
--- a/indoteknik_api/controllers/api_v1/stock_picking.py
+++ b/indoteknik_api/controllers/api_v1/stock_picking.py
@@ -8,6 +8,8 @@ import logging
_logger = logging.getLogger(__name__)
+_logger = logging.getLogger(__name__)
+
class StockPicking(controller.Controller):
prefix = '/api/v1/'
PREFIX_PARTNER = prefix + 'partner/<partner_id>/'
@@ -145,7 +147,6 @@ class StockPicking(controller.Controller):
'name': picking_data.name
})
-
@http.route(prefix + 'webhook/biteship', type='json', auth='public', methods=['POST'], csrf=False)
def update_status_from_biteship(self, **kw):
_logger.info("Biteship Webhook: Request received at controller start (type='json').")
@@ -180,18 +181,6 @@ class StockPicking(controller.Controller):
# Untuk error, kembalikan dictionary error juga, Odoo akan mengonversinya ke JSON
return {'status': 'error', 'message': str(e)}
- # @http.route(prefix + 'webhook/biteship', auth='public', methods=['POST'], csrf=False)
- # def udpate_status_from_bitehsip(self, **kw):
- # return "ok"
-
- # def process_order_status(self, data):
- # picking_model = request.env['stock.picking'].sudo().search([('biteship_id', '=', data.get('order_id'))],
- # limit=1)
- # if data.get('status') == 'picked':
- # picking_model.write({'driver_departure_date': datetime.utcnow()})
- # elif data.get('status') == 'delivered':
- # picking_model.write({'driver_arrival_date': datetime.utcnow()})
-
def process_order_status(self, data):
picking = request.env['stock.picking'].sudo().search([
('biteship_id', '=', data.get('order_id'))
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py
index ad019d4b..17cec7b6 100755
--- a/indoteknik_custom/__manifest__.py
+++ b/indoteknik_custom/__manifest__.py
@@ -156,6 +156,7 @@
'views/stock_backorder_confirmation_views.xml',
'views/barcoding_product.xml',
'views/project_views.xml',
+ 'views/approval_payment_term.xml',
'report/report.xml',
'report/report_banner_banner.xml',
'report/report_banner_banner2.xml',
diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py
index 3f538e25..b815b472 100755
--- a/indoteknik_custom/models/__init__.py
+++ b/indoteknik_custom/models/__init__.py
@@ -151,4 +151,5 @@ from . import account_payment_register
from . import stock_inventory
from . import sale_order_delay
from . import approval_invoice_date
+from . import approval_payment_term
# from . import patch
diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py
index 66020a69..b6627867 100644
--- a/indoteknik_custom/models/account_move.py
+++ b/indoteknik_custom/models/account_move.py
@@ -70,6 +70,7 @@ class AccountMove(models.Model):
reklas_misc_id = fields.Many2one('account.move', string='Journal Entries Reklas')
# Di model account.move
bill_id = fields.Many2one('account.move', string='Vendor Bill', domain=[('move_type', '=', 'in_invoice')], help='Bill asal dari proses reklas ini')
+ down_payment = fields.Boolean('Down Payments?')
# def name_get(self):
@@ -98,12 +99,27 @@ class AccountMove(models.Model):
self.invoice_date = self.date
+ # def compute_length_of_payment(self):
+ # for rec in self:
+ # payment_term = rec.invoice_payment_term_id.line_ids[0].days
+ # terima_faktur = rec.date_terima_tukar_faktur
+ # payment = self.search([('ref', '=', rec.name), ('move_type', '=', 'entry')], limit=1)
+
+ # if payment and terima_faktur:
+ # date_diff = terima_faktur - payment.date
+ # rec.length_of_payment = date_diff.days + payment_term
+ # else:
+ # rec.length_of_payment = 0
+
def compute_length_of_payment(self):
for rec in self:
- payment_term = rec.invoice_payment_term_id.line_ids[0].days
+ payment_term = 0
+ if rec.invoice_payment_term_id and rec.invoice_payment_term_id.line_ids:
+ payment_term = rec.invoice_payment_term_id.line_ids[0].days
+
terima_faktur = rec.date_terima_tukar_faktur
payment = self.search([('ref', '=', rec.name), ('move_type', '=', 'entry')], limit=1)
-
+
if payment and terima_faktur:
date_diff = terima_faktur - payment.date
rec.length_of_payment = date_diff.days + payment_term
@@ -436,18 +452,18 @@ class AccountMove(models.Model):
return invoices
def export_faktur_to_xml(self):
-
valid_invoices = self
- # Panggil model coretax.faktur untuk menghasilkan XML
coretax_faktur = self.env['coretax.faktur'].create({})
- response = coretax_faktur.export_to_download(invoices=valid_invoices)
- current_time = datetime.utcnow()
- # Tandai faktur sebagai sudah diekspor
+ response = coretax_faktur.export_to_download(
+ invoices=valid_invoices,
+ down_payments=[inv.down_payment for inv in valid_invoices],
+ )
+
valid_invoices.write({
'is_efaktur_exported': True,
- 'date_efaktur_exported': current_time, # Set tanggal ekspor
+ 'date_efaktur_exported': datetime.utcnow(),
})
- return response
+ return response \ No newline at end of file
diff --git a/indoteknik_custom/models/approval_payment_term.py b/indoteknik_custom/models/approval_payment_term.py
new file mode 100644
index 00000000..81eb1908
--- /dev/null
+++ b/indoteknik_custom/models/approval_payment_term.py
@@ -0,0 +1,82 @@
+from odoo import models, api, fields
+from odoo.exceptions import AccessError, UserError, ValidationError
+from datetime import timedelta, date, datetime
+import logging
+
+_logger = logging.getLogger(__name__)
+
+class ApprovalPaymentTerm(models.Model):
+ _name = "approval.payment.term"
+ _description = "Approval Payment Term"
+ _inherit = ['mail.thread']
+ _rec_name = 'number'
+
+ number = fields.Char(string='Document No', index=True, copy=False, readonly=True, tracking=True)
+ partner_id = fields.Many2one('res.partner', string='Partner', copy=False)
+ property_payment_term_id = fields.Many2one('account.payment.term', string='Payment Term', copy=False, tracking=True)
+ parent_id = fields.Many2one('res.partner', string='Related Company', copy=False)
+ blocking_stage = fields.Float(string='Blocking Amount',
+ help="Cannot make sales once the selected "
+ "customer is crossed blocking amount."
+ "Set its value to 0.00 to disable "
+ "this feature", tracking=True, copy=False)
+ warning_stage = fields.Float(string='Warning Amount',
+ help="A warning message will appear once the "
+ "selected customer is crossed warning "
+ "amount. Set its value to 0.00 to"
+ " disable this feature", tracking=True, copy=False)
+ active_limit = fields.Boolean('Active Credit Limit', copy=False, tracking=True)
+ approve_sales_manager = fields.Boolean('Approve Sales Manager', tracking=True, copy=False)
+ approve_finance = fields.Boolean('Approve Finance', tracking=True, copy=False)
+ approve_leader = fields.Boolean('Approve Pimpinan', tracking=True, copy=False)
+ reason = fields.Text('Reason', tracking=True)
+ approve_date = fields.Datetime('Approve Date')
+
+
+ @api.constrains('partner_id')
+ def constrains_partner_id(self):
+ if self.partner_id:
+ self.parent_id = self.partner_id.parent_id.id if self.partner_id.parent_id else None
+ self.blocking_stage = self.partner_id.blocking_stage
+ self.warning_stage = self.partner_id.warning_stage
+ self.active_limit = self.partner_id.active_limit
+ self.property_payment_term_id = self.partner_id.property_payment_term_id.id
+
+ def button_approve(self):
+ user = self.env.user
+ is_it = user.has_group('indoteknik_custom.group_role_it')
+
+ if is_it or user.id == 19:
+ self.approve_sales_manager = True
+ return
+
+ if is_it or user.id == 688 and self.approve_sales_manager:
+ self.approve_finance = True
+ return
+
+ if is_it or user.id == 7 and self.approve_sales_manager and self.approve_finance:
+ self.approve_leader = True
+
+ if not is_it or not self.approve_finance:
+ raise UserError('Harus Approval Finance!!')
+ if not is_it or not self.approve_leader:
+ raise UserError('Harus Approval Pimpinan!!')
+
+ if user.id == 7:
+ if not self.approve_finance:
+ raise UserError('Belum Di Approve Oleh Finance')
+
+ if self.approve_leader == True:
+ self.partner_id.write({
+ 'blocking_stage': self.blocking_stage,
+ 'warning_stage': self.warning_stage,
+ 'active_limit': self.active_limit,
+ 'property_payment_term_id': self.property_payment_term_id.id
+ })
+ self.approve_date = datetime.utcnow()
+
+ @api.model
+ def create(self, vals):
+ vals['number'] = self.env['ir.sequence'].next_by_code('approval.payment.term') or '0'
+ result = super(ApprovalPaymentTerm, self).create(vals)
+ return result
diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py
index c9edf07c..83a7cb3c 100644
--- a/indoteknik_custom/models/automatic_purchase.py
+++ b/indoteknik_custom/models/automatic_purchase.py
@@ -486,7 +486,7 @@ class AutomaticPurchase(models.Model):
# _logger.info('test %s' % point.product_id.name)
if point.product_id.qty_available_bandengan > point.product_min_qty:
continue
- qty_purchase = point.product_max_qty - point.product_id.qty_incoming_bandengan - point.product_id.qty_onhand_bandengan
+ qty_purchase = point.product_max_qty - point.product_id.qty_incoming_bandengan - point.product_id.qty_available_bandengan
po_line = self.env['purchase.order.line'].search([('product_id', '=', point.product_id.id), ('order_id.state', '=', 'done')], order='id desc', limit=1)
if self.vendor_id:
diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py
index 03d32d2d..97184cdb 100644
--- a/indoteknik_custom/models/commision.py
+++ b/indoteknik_custom/models/commision.py
@@ -148,6 +148,7 @@ class CustomerCommision(models.Model):
_order = 'id desc'
_inherit = ['mail.thread']
_rec_name = 'number'
+ _description = 'Customer Benefits'
number = fields.Char(string='Document No', index=True, copy=False, readonly=True)
date_from = fields.Date(string='Date From', required=True)
@@ -175,12 +176,22 @@ class CustomerCommision(models.Model):
('approved', 'Approved'),
('reject', 'Rejected'),
], string='Status')
- commision_percent = fields.Float(string='Commision %', tracking=3)
- commision_amt = fields.Float(string='Commision Amount', tracking=3)
- cashback = fields.Float(string='Cashback', compute="compute_cashback")
- total_commision = fields.Float(string='Total Commision', compute="compute_cashback")
+
+ # commision_percent = fields.Float(string='Commision %', tracking=3)
+ commision_percent = fields.Float(string='Persentase (%)', tracking=3)
+
+ # commision_amt = fields.Float(string='Commision Amount', tracking=3)
+ commision_amt = fields.Float(string='Amount', tracking=3)
+
+ # cashback = fields.Float(string='Cashback', compute="compute_cashback")
+ cashback = fields.Float(string='PPh Cashback', compute="compute_cashback")
+
+ # total_commision = fields.Float(string='Total Commision', compute="compute_cashback")
+ total_commision = fields.Float(string='Cashback yang dibayarkan', compute="compute_cashback")
+
total_cashback = fields.Float(string='Total Cashback')
- commision_amt_text = fields.Char(string='Commision Amount Text', compute='compute_delivery_amt_text')
+ commision_amt_text = fields.Char(string='Amount Text', compute='compute_delivery_amt_text')
+ total_cashback_text = fields.Char(string='Cashback Text', compute='compute_total_cashback_text')
total_dpp = fields.Float(string='Total DPP', compute='_compute_total_dpp')
commision_type = fields.Selection([
('fee', 'Fee'),
@@ -271,6 +282,20 @@ class CustomerCommision(models.Model):
except:
record.commision_amt_text = res
+ def compute_total_cashback_text(self):
+ tb = Terbilang()
+
+ for record in self:
+ res = ''
+ try:
+ if record.total_commision > 0:
+ tb.parse(int(record.total_commision))
+ res = tb.getresult().title()
+ record.total_cashback_text = f"{res} Rupiah" if res else ""
+ except Exception as e:
+ record.total_cashback_text = ""
+ _logger.error("Error computing cashback text: %s", str(e))
+
def _compute_grouped_numbers(self):
for rec in self:
so_numbers = set()
@@ -348,14 +373,31 @@ class CustomerCommision(models.Model):
@api.model
def create(self, vals):
- vals['number'] = self.env['ir.sequence'].next_by_code('customer.commision') or '0'
- # if vals['commision_amt'] > 0:
- # commision_amt = vals['commision_amt']
- # total_dpp = vals['total_dpp']
- # commision_percent = commision_amt / total_dpp * 100
- # vals['commision_percent'] = commision_percent
- result = super(CustomerCommision, self).create(vals)
- return result
+ commision_type = vals.get('commision_type')
+
+ if commision_type == 'cashback':
+ sequence_code = 'customer.commision.cashback'
+ elif commision_type == 'fee':
+ sequence_code = 'customer.commision.fee'
+ elif commision_type == 'rebate':
+ sequence_code = 'customer.commision.rebate'
+ else:
+ raise UserError('Tipe komisi tidak dikenal!')
+
+ vals['number'] = self.env['ir.sequence'].next_by_code(sequence_code) or '0'
+
+ return super(CustomerCommision, self).create(vals)
+
+ # @api.model
+ # def create(self, vals):
+ # vals['number'] = self.env['ir.sequence'].next_by_code('customer.commision') or '0'
+ # # if vals['commision_amt'] > 0:
+ # # commision_amt = vals['commision_amt']
+ # # total_dpp = vals['total_dpp']
+ # # commision_percent = commision_amt / total_dpp * 100
+ # # vals['commision_percent'] = commision_percent
+ # result = super(CustomerCommision, self).create(vals)
+ # return result
def action_confirm_customer_commision(self):
jakarta_tz = pytz.timezone('Asia/Jakarta')
diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py
index 92ff1a72..ce94306f 100644
--- a/indoteknik_custom/models/coretax_fatur.py
+++ b/indoteknik_custom/models/coretax_fatur.py
@@ -3,6 +3,9 @@ import xml.etree.ElementTree as ET
from xml.dom import minidom
import base64
import re
+import logging
+
+_logger = logging.getLogger(__name__)
class CoretaxFaktur(models.Model):
@@ -32,7 +35,7 @@ class CoretaxFaktur(models.Model):
return cleaned_number
- def generate_xml(self, invoices=None):
+ def generate_xml(self, invoices=None, down_payments=False):
# Buat root XML
root = ET.Element('TaxInvoiceBulk', {
'xmlns:xsi': "http://www.w3.org/2001/XMLSchema-instance",
@@ -72,59 +75,96 @@ class CoretaxFaktur(models.Model):
ET.SubElement(tax_invoice, 'BuyerEmail').text = invoice.partner_id.email or ''
ET.SubElement(tax_invoice, 'BuyerIDTKU').text = buyerIDTKU
- # Filter product
- product_lines = invoice.invoice_line_ids.filtered(
- lambda l: not l.display_type and hasattr(l, 'account_id') and
- l.account_id and l.product_id and
- l.account_id.id != self.DISCOUNT_ACCOUNT_ID and
- l.quantity != -1
- )
+ _logger.info(" invoice down_payments: %s", invoice.down_payment)
+ # Handle product lines based on down_payments flag
+ if invoice.down_payment and invoice.invoice_origin:
+ # Get from sale.order.line for down payment
+ sale_order = invoice.sale_id
+ if sale_order:
+ product_lines = sale_order.order_line.filtered(
+ lambda l: l.product_id and not l.is_downpayment and not l.display_type and not l.product_id.id == 229625
+ )
+ # Convert sale order lines to invoice-like format
+ converted_lines = []
+ for line in product_lines:
+ converted_lines.append({
+ 'name': line.name,
+ 'product_id': line.product_id,
+ 'price_subtotal': line.price_subtotal,
+ 'quantity': line.product_uom_qty,
+ 'price_unit': line.price_unit,
+ 'account_id': line.order_id.analytic_account_id or False,
+ })
+ product_lines = converted_lines
+ else:
+ product_lines = []
+ else:
+ # Normal case - get from invoice lines
+ product_lines = invoice.invoice_line_ids.filtered(
+ lambda l: not l.display_type and hasattr(l, 'account_id') and
+ l.account_id and l.product_id and
+ l.account_id.id != self.DISCOUNT_ACCOUNT_ID and
+ l.quantity != -1
+ )
- # Filter discount
+ # Filter discount (always from invoice)
discount_lines = invoice.invoice_line_ids.filtered(
lambda l: not l.display_type and (
- (hasattr(l, 'account_id') and l.account_id and
- l.account_id.id == self.DISCOUNT_ACCOUNT_ID) or
- (l.quantity == -1)
+ (hasattr(l, 'account_id') and l.account_id and
+ l.account_id.id == self.DISCOUNT_ACCOUNT_ID) or
+ (l.quantity == -1)
)
)
- # Calculate total product amount (before discount)
- total_product_amount = sum(line.price_subtotal for line in product_lines)
+ # Calculate totals
+ total_product_amount = sum(line.get('price_subtotal', 0) if isinstance(line, dict)
+ else line.price_subtotal for line in product_lines)
if total_product_amount == 0:
total_product_amount = 1 # Avoid division by zero
- # Calculate total discount amount
total_discount_amount = abs(sum(line.price_subtotal for line in discount_lines))
# Tambahkan elemen ListOfGoodService
list_of_good_service = ET.SubElement(tax_invoice, 'ListOfGoodService')
for line in product_lines:
+ # Handle both dict (converted sale lines) and normal invoice lines
+ if isinstance(line, dict):
+ line_price_subtotal = line['price_subtotal']
+ line_quantity = line['quantity']
+ line_name = line['name']
+ line_price_unit = line['price_unit']
+ else:
+ line_price_subtotal = line.price_subtotal
+ line_quantity = line.quantity
+ line_name = line.name
+ line_price_unit = line.price_unit
+
# Calculate prorated discount
- line_proportion = line.price_subtotal / total_product_amount
+ line_proportion = line_price_subtotal / total_product_amount
line_discount = total_discount_amount * line_proportion
- # unit_price = line.price_unit
- subtotal = line.price_subtotal
- quantity = line.quantity
+ subtotal = line_price_subtotal
+ quantity = line_quantity
total_discount = round(line_discount, 2)
# Calculate other tax values
otherTaxBase = round(subtotal * (11 / 12), 2) if subtotal else 0
vat_amount = round(otherTaxBase * 0.12, 2)
+ price_per_unit = round(subtotal / quantity, 2) if quantity else 0
+
# Create the line in XML
good_service = ET.SubElement(list_of_good_service, 'GoodService')
ET.SubElement(good_service, 'Opt').text = 'A'
ET.SubElement(good_service, 'Code').text = '000000'
- ET.SubElement(good_service, 'Name').text = line.name
+ ET.SubElement(good_service, 'Name').text = line_name
ET.SubElement(good_service, 'Unit').text = 'UM.0018'
- ET.SubElement(good_service, 'Price').text = str(round(subtotal / quantity, 2)) if subtotal else '0'
+ # ET.SubElement(good_service, 'Price').text = str(round(line_price_unit, 2)) if line_price_unit else '0'
+ ET.SubElement(good_service, 'Price').text = str(price_per_unit)
ET.SubElement(good_service, 'Qty').text = str(quantity)
ET.SubElement(good_service, 'TotalDiscount').text = str(total_discount)
- ET.SubElement(good_service, 'TaxBase').text = str(
- round(subtotal)) if subtotal else '0'
+ ET.SubElement(good_service, 'TaxBase').text = str(round(subtotal)) if subtotal else '0'
ET.SubElement(good_service, 'OtherTaxBase').text = str(otherTaxBase)
ET.SubElement(good_service, 'VATRate').text = '12'
ET.SubElement(good_service, 'VAT').text = str(vat_amount)
@@ -136,9 +176,9 @@ class CoretaxFaktur(models.Model):
xml_pretty = minidom.parseString(xml_str).toprettyxml(indent=" ")
return xml_pretty
- def export_to_download(self, invoices):
+ def export_to_download(self, invoices, down_payments):
# Generate XML content
- xml_content = self.generate_xml(invoices)
+ xml_content = self.generate_xml(invoices, down_payments)
# Encode content to Base64
xml_encoded = base64.b64encode(xml_content.encode('utf-8'))
diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py
index 14821f27..85b8405f 100644
--- a/indoteknik_custom/models/mrp_production.py
+++ b/indoteknik_custom/models/mrp_production.py
@@ -110,8 +110,9 @@ class MrpProduction(models.Model):
'picking_type_id': 28, # indoteknik bandengan receipts
'date_order': current_time,
'product_bom_id': self.product_id.id,
- # 'sale_order_id': self.sale_order_id.id,
- 'note_description': 'from Manufacturing Order'
+ 'sale_order_id': self.sale_order.id,
+ 'manufacturing_id': self.id,
+ 'note_description': 'from Manufacturing Order',
}
domain = [
diff --git a/indoteknik_custom/models/patch/http_override.py b/indoteknik_custom/models/patch/http_override.py
index e1978edb..6bec1343 100644
--- a/indoteknik_custom/models/patch/http_override.py
+++ b/indoteknik_custom/models/patch/http_override.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import odoo.http
import json
import logging
diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py
index 1a7e50f8..4dc26d74 100755
--- a/indoteknik_custom/models/purchase_order.py
+++ b/indoteknik_custom/models/purchase_order.py
@@ -97,46 +97,69 @@ class PurchaseOrder(models.Model):
string="BU Related Count",
compute='_compute_bu_related_count'
)
+ manufacturing_id = fields.Many2one('mrp.production', string='Manufacturing Orders')
@api.depends('name')
def _compute_bu_related_count(self):
+ StockPicking = self.env['stock.picking']
for order in self:
if not order.name:
order.bu_related_count = 0
continue
- # BU langsung dari PO
- base_bu = self.env['stock.picking'].search([
+ # Ambil semua BU awal dari PO
+ base_bu = StockPicking.search([
('name', 'ilike', 'BU/'),
('origin', 'ilike', order.name)
])
- base_names = base_bu.mapped('name')
- # Return dari BU di atas
- return_bu = self.env['stock.picking'].search([
- ('origin', 'in', [f"Return of {name}" for name in base_names])
- ])
+ all_bu = base_bu
+ seen_names = set(base_bu.mapped('name'))
+
+ # Loop rekursif untuk mencari seluruh return BU
+ while True:
+ next_bu = StockPicking.search([
+ ('name', 'ilike', 'BU/'),
+ ('origin', 'in', ['Return of %s' % name for name in seen_names])
+ ])
+ next_names = set(next_bu.mapped('name'))
+
+ if not next_names - seen_names:
+ break
+
+ all_bu |= next_bu
+ seen_names |= next_names
+
+ order.bu_related_count = len(all_bu)
- order.bu_related_count = len(base_bu) + len(return_bu)
def action_view_related_bu(self):
self.ensure_one()
+ StockPicking = self.env['stock.picking']
+
# Step 1: cari semua BU pertama (PUT, INT) yang berasal dari PO ini
- base_bu = self.env['stock.picking'].search([
+ base_bu = StockPicking.search([
('name', 'ilike', 'BU/'),
('origin', 'ilike', self.name)
])
- base_bu_names = base_bu.mapped('name')
- # Step 2: cari BU turunan (seperti BU/VRT) yang origin-nya mengandung nama BU tersebut
- domain = [
- '|',
- '&',
- ('name', 'ilike', 'BU/'),
- ('origin', 'ilike', self.name),
- ('origin', 'in', [f"Return of {name}" for name in base_bu_names])
- ]
+ all_bu = base_bu
+ seen_names = set(base_bu.mapped('name'))
+
+ # Step 2: Loop rekursif cari BU dengan origin 'Return of {name}'
+ while True:
+ next_bu = StockPicking.search([
+ ('name', 'ilike', 'BU/'),
+ ('origin', 'in', ['Return of %s' % name for name in seen_names])
+ ])
+ next_names = set(next_bu.mapped('name'))
+
+ if not next_names - seen_names:
+ break
+
+ all_bu |= next_bu
+ seen_names |= next_names
return {
'name': 'Related BU (INT/PRT/PUT/VRT)',
@@ -144,7 +167,7 @@ class PurchaseOrder(models.Model):
'res_model': 'stock.picking',
'view_mode': 'tree,form',
'target': 'current',
- 'domain': domain,
+ 'domain': [('id', 'in', list(all_bu.ids))],
}
diff --git a/indoteknik_custom/models/purchase_order_sales_match.py b/indoteknik_custom/models/purchase_order_sales_match.py
index 0bd0092b..b18864f3 100644
--- a/indoteknik_custom/models/purchase_order_sales_match.py
+++ b/indoteknik_custom/models/purchase_order_sales_match.py
@@ -28,6 +28,20 @@ class PurchaseOrderSalesMatch(models.Model):
purchase_price_po = fields.Float('Purchase Price PO', compute='_compute_purchase_price_po')
purchase_line_id = fields.Many2one('purchase.order.line', string='Purchase Line', compute='_compute_purchase_line_id')
hold_outgoing_so = fields.Boolean(string='Hold Outgoing SO', related='sale_id.hold_outgoing')
+ bu_pick = fields.Many2one('stock.picking', string='BU Pick', compute='compute_bu_pick')
+
+ def compute_bu_pick(self):
+ for rec in self:
+ stock_move = self.env['stock.move'].search([
+ ('reference', 'ilike', 'BU/PICK'),
+ ('state', 'in', ['confirmed','waiting','partially_available']),
+ ('product_id', '=', rec.product_id.id),
+ ('sale_line_id', '=', rec.sale_line_id.id),
+ ])
+ if stock_move:
+ rec.bu_pick = stock_move.picking_id.id
+ else:
+ rec.bu_pick = None
def _compute_purchase_line_id(self):
for line in self:
diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py
index 9986b9c0..f5347bea 100644
--- a/indoteknik_custom/models/res_partner.py
+++ b/indoteknik_custom/models/res_partner.py
@@ -164,6 +164,22 @@ class ResPartner(models.Model):
"Set its value to 0.00 to disable "
"this feature", tracking=3)
telegram_id = fields.Char(string="Telegram")
+ avg_aging= fields.Float(string='Average Aging')
+ payment_difficulty = fields.Selection([('bermasalah', 'Bermasalah'),('sulit', 'Sulit'),('agak_sulit', 'Agak Sulit'),('normal', 'Normal')], string='Payment Difficulty', compute="_compute_payment_difficulty", inverse = "_inverse_payment_difficulty", tracking=3)
+ payment_history_url = fields.Text(string='Payment History URL')
+
+ @api.depends('parent_id.payment_difficulty')
+ def _compute_payment_difficulty(self):
+ for partner in self:
+ if partner.parent_id:
+ partner.payment_difficulty = partner.parent_id.payment_difficulty
+
+ def _inverse_payment_difficulty(self):
+ for partner in self:
+ if not partner.parent_id:
+ partner.child_ids.write({
+ 'payment_difficulty': partner.payment_difficulty
+ })
@api.model
def _default_payment_term(self):
@@ -192,6 +208,10 @@ class ResPartner(models.Model):
for rec in self:
if 'latitude' in vals or 'longtitude' in vals:
rec._update_address_from_coords()
+
+ # Sinkronisasi payment_difficulty ke semua anak jika partner ini adalah parent
+ if not rec.parent_id and 'payment_difficulty' in vals:
+ rec.child_ids.write({'payment_difficulty': vals['payment_difficulty']})
#
# # if 'property_payment_term_id' in vals:
# # if not self.env.user.is_accounting and vals['property_payment_term_id'] != 26:
@@ -210,6 +230,8 @@ class ResPartner(models.Model):
for rec in records:
if vals.get('latitude') and vals.get('longtitude'):
rec._update_address_from_coords()
+ if rec.parent_id and not vals.get('payment_difficulty'):
+ rec.payment_difficulty = rec.parent_id.payment_difficulty
return records
@api.constrains('name')
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index 6da46398..591951ca 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -382,13 +382,15 @@ class SaleOrder(models.Model):
# Simpan ke field sebagai UTC-naive datetime (standar Odoo)
order.et_products = eta_datetime.astimezone(pytz.utc).replace(tzinfo=None)
- @api.depends('picking_ids.state', 'picking_ids.date_reserved')
+ @api.depends('picking_ids.state', 'picking_ids.date_done')
def _compute_eta_date_reserved(self):
for order in self:
pickings = order.picking_ids.filtered(
- lambda p: p.state == 'assigned' and p.date_reserved and 'BU/PICK/' in (p.name or '')
+ lambda p: p.state in ('assigned', 'done') and p.date_reserved and 'BU/PICK/' in (p.name or '')
)
- order.eta_date_reserved = min(pickings.mapped('date_reserved')) if pickings else False
+ done_dates = [d for d in pickings.mapped('date_done') if d]
+ order.eta_date_reserved = min(done_dates) if done_dates else False
+ # order.eta_date_reserved = min(pickings.mapped('date_done')) if pickings else False
@api.onchange('shipping_cost_covered')
def _onchange_shipping_cost_covered(self):
@@ -591,11 +593,11 @@ class SaleOrder(models.Model):
if shipping_option.exists():
courier_service = shipping_option.courier_service_code
vals['delivery_service_type'] = courier_service
- _logger.info("🛰️ Set delivery_service_type: %s from shipping_option_id: %s", courier_service, shipping_option_id)
+ _logger.info("Set delivery_service_type: %s from shipping_option_id: %s", courier_service, shipping_option_id)
else:
- _logger.warning("⚠️ shipping_option_id %s not found or invalid.", shipping_option_id)
+ _logger.warning("shipping_option_id %s not found or invalid.", shipping_option_id)
else:
- _logger.info("ℹ️ shipping_option_id not found in vals or record.")
+ _logger.info("shipping_option_id not found in vals or record.")
# @api.model
# def fields_get(self, allfields=None, attributes=None):
@@ -845,25 +847,36 @@ class SaleOrder(models.Model):
if total_weight == 0:
raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.")
- destination_subsdistrict_id = self.real_shipping_id.kecamatan_id.rajaongkir_id
+ kecamatan_name = self.real_shipping_id.kecamatan_id.name
+ kota_name = self.real_shipping_id.kota_id.name
+ kelurahan_name = self.real_shipping_id.kelurahan_id.name
+
+ destination_subsdistrict_id = self._get_subdistrict_id_from_komerce(kecamatan_name, kota_name, kelurahan_name)
+
+ # destination_subsdistrict_id = self.real_shipping_id.kecamatan_id.rajaongkir_id
if not destination_subsdistrict_id:
raise UserError("Gagal mendapatkan ID kota tujuan.")
result = self._call_rajaongkir_api(total_weight, destination_subsdistrict_id)
+ if not result or not result.get('data'):
+ raise UserError(_("Kurir %s tidak tersedia untuk tujuan ini. Silakan pilih kurir lain.") % self.carrier_id.name)
+
if result:
shipping_options = []
- for courier in result['rajaongkir']['results']:
- for cost_detail in courier['costs']:
- service = cost_detail['service']
- description = cost_detail['description']
- etd = cost_detail['cost'][0]['etd']
- value = cost_detail['cost'][0]['value']
- shipping_options.append((service, description, etd, value, courier['code']))
-
+
+ for cost in result.get('data', []):
+ service = cost.get('service')
+ description = cost.get('description')
+ etd = cost.get('etd', '')
+ value = cost.get('cost', 0)
+ provider = cost.get('code')
+
+ shipping_options.append((service, description, etd, value, provider))
+
self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink()
_logger.info(f"Shipping options: {shipping_options}")
-
+
for service, description, etd, value, provider in shipping_options:
self.env["shipping.option"].create({
"name": service,
@@ -873,19 +886,15 @@ class SaleOrder(models.Model):
"sale_order_id": self.id,
})
-
self.shipping_option_id = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1).id
_logger.info(f"Shipping option SO ID: {self.shipping_option_id}")
self.message_post(
body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}<br/>Detail Lain:<br/>"
- f"{'<br/>'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}",
+ f"{'<br/>'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]}, Cost: Rp {s[3]}' for s in shipping_options])}",
message_type="comment"
)
-
- # self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}<br/>Detail Lain:<br/>{'<br/>'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", message_type="comment")
-
else:
raise UserError("Gagal mendapatkan estimasi ongkir.")
@@ -1191,25 +1200,30 @@ class SaleOrder(models.Model):
def _call_rajaongkir_api(self, total_weight, destination_subsdistrict_id):
- url = 'https://pro.rajaongkir.com/api/cost'
+ url = 'https://rajaongkir.komerce.id/api/v1/calculate/domestic-cost'
headers = {
'key': '9b1310f644056d84d60b0af6bb21611a',
}
courier = self.carrier_id.name.lower()
data = {
- 'origin': 2127,
- 'originType': 'subdistrict',
+ 'origin': 17656,
+ # 'originType': 'subdistrict',
'destination': int(destination_subsdistrict_id),
- 'destinationType': 'subdistrict',
+ # 'destinationType': 'subdistrict',
'weight': int(total_weight * 1000),
'courier': courier,
}
- response = requests.post(url, headers=headers, data=data)
- if response.status_code == 200:
- return response.json()
- return None
+ try:
+ _logger.info(f"Calling RajaOngkir API with data: {data}")
+ response = requests.post(url, headers=headers, data=data)
+ _logger.info(f"RajaOngkir response: {response.status_code} - {response.text}")
+
+ if response.status_code == 200:
+ return response.json()
+ except Exception as e:
+ _logger.error(f"Exception while calling RajaOngkir: {str(e)}")
def _normalize_city_name(self, city_name):
city_name = city_name.lower()
@@ -1223,37 +1237,82 @@ class SaleOrder(models.Model):
return city_name
- def _get_city_id_by_name(self, city_name):
- url = 'https://pro.rajaongkir.com/api/city'
+ # def _get_city_id_by_name(self, city_name):
+ # url = 'https://pro.rajaongkir.com/api/city'
+ # headers = {
+ # 'key': '9b1310f644056d84d60b0af6bb21611a',
+ # }
+
+ # normalized_city_name = self._normalize_city_name(city_name)
+
+ # response = requests.get(url, headers=headers)
+ # if response.status_code == 200:
+ # city_data = response.json()
+ # for city in city_data['rajaongkir']['results']:
+ # if city['city_name'].lower() == normalized_city_name:
+ # return city['city_id']
+ # return None
+
+ # def _get_subdistrict_id_by_name(self, city_id, subdistrict_name):
+ # url = f'https://pro.rajaongkir.com/api/subdistrict?city={city_id}'
+ # headers = {
+ # 'key': '9b1310f644056d84d60b0af6bb21611a',
+ # }
+
+ # response = requests.get(url, headers=headers)
+ # if response.status_code == 200:
+ # subdistrict_data = response.json()
+ # for subdistrict in subdistrict_data['rajaongkir']['results']:
+ # subsdistrict_1 = subdistrict['subdistrict_name'].lower()
+ # subsdistrict_2 = subdistrict_name.lower()
+
+ # if subsdistrict_1 == subsdistrict_2:
+ # return subdistrict['subdistrict_id']
+ # return None
+
+ def _get_subdistrict_id_from_komerce(self, kecamatan_name, kota_name, kelurahan_name=None):
+ url = 'https://rajaongkir.komerce.id/api/v1/destination/domestic-destination'
headers = {
'key': '9b1310f644056d84d60b0af6bb21611a',
}
- normalized_city_name = self._normalize_city_name(city_name)
-
- response = requests.get(url, headers=headers)
- if response.status_code == 200:
- city_data = response.json()
- for city in city_data['rajaongkir']['results']:
- if city['city_name'].lower() == normalized_city_name:
- return city['city_id']
- return None
+ if kelurahan_name:
+ search = f"{kelurahan_name} {kecamatan_name} {kota_name}"
+ else:
+ search = f"{kecamatan_name} {kota_name}"
- def _get_subdistrict_id_by_name(self, city_id, subdistrict_name):
- url = f'https://pro.rajaongkir.com/api/subdistrict?city={city_id}'
- headers = {
- 'key': '9b1310f644056d84d60b0af6bb21611a',
+ params = {
+ 'search': search,
+ 'limit': 5
}
- response = requests.get(url, headers=headers)
- if response.status_code == 200:
- subdistrict_data = response.json()
- for subdistrict in subdistrict_data['rajaongkir']['results']:
- subsdistrict_1 = subdistrict['subdistrict_name'].lower()
- subsdistrict_2 = subdistrict_name.lower()
+ try:
+ response = requests.get(url, headers=headers, params=params, timeout=10)
+ if response.status_code == 200:
+ data = response.json().get('data', [])
+ _logger.info(f"[Komerce] Fetched {len(data)} subdistricts for search '{search}'")
+ _logger.info(f"[Komerce] Response: {data}")
+
+ normalized_kota = self._normalize_city_name(kota_name)
+
+ for item in data:
+ match_kelurahan = (
+ not kelurahan_name or
+ item.get('subdistrict_name', '').lower() == kelurahan_name.lower()
+ )
+ if (
+ match_kelurahan and
+ item.get('district_name', '').lower() == kecamatan_name.lower() and
+ item.get('city_name', '').lower() == normalized_kota
+ ):
+ return item.get('id')
+
+ _logger.warning(f"[Komerce] No match for '{kecamatan_name}' in city '{kota_name}' with kelurahan '{kelurahan_name}'")
+ else:
+ _logger.error(f"[Komerce] HTTP Error {response.status_code}: {response.text}")
+ except Exception as e:
+ _logger.error(f"[Komerce] Exception: {e}")
- if subsdistrict_1 == subsdistrict_2:
- return subdistrict['subdistrict_id']
return None
def _compute_type_promotion(self):
@@ -1592,6 +1651,7 @@ class SaleOrder(models.Model):
'campaign_id': self.campaign_id.id,
'medium_id': self.medium_id.id,
'source_id': self.source_id.id,
+ 'down_payment': 229625 in [line.product_id.id for line in self.order_line],
'user_id': self.user_id.id,
'sale_id': self.id,
'invoice_user_id': self.user_id.id,
@@ -1993,9 +2053,9 @@ class SaleOrder(models.Model):
confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed' or x.state == 'done')
if not confirmed_bom:
raise UserError(
- "Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.")
+ "Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi Purchasing.")
else:
- raise UserError("Product BOM tidak di temukan di manufacturing orders, silahkan hubungi MD")
+ raise UserError("Product BOM tidak di temukan di manufacturing orders, silahkan hubungi Purchasing")
def check_duplicate_product(self):
for order in self:
diff --git a/indoteknik_custom/models/sale_orders_multi_update.py b/indoteknik_custom/models/sale_orders_multi_update.py
index 95cfde21..962f60b5 100644
--- a/indoteknik_custom/models/sale_orders_multi_update.py
+++ b/indoteknik_custom/models/sale_orders_multi_update.py
@@ -11,6 +11,13 @@ class SaleOrdersMultiUpdate(models.TransientModel):
sale_ids = self._context['sale_ids']
sales = self.env['sale.order'].browse(sale_ids)
sales.action_multi_update_invoice_status()
+
+ for sale in sales:
+ sale.message_post(
+ body="Sales Order has been marked as Completed",
+ message_type="comment"
+ )
+
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index d4167609..d25208d1 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -1316,7 +1316,6 @@ class StockPicking(models.Model):
self.final_seq = 0
self.set_picking_code_out()
self.send_koli_to_so()
-
if (self.state_reserve == 'done' and self.picking_type_code == 'internal' and 'BU/PICK/' in self.name
and self.linked_manual_bu_out):
if not self.linked_manual_bu_out.date_reserved:
@@ -1353,7 +1352,20 @@ class StockPicking(models.Model):
'target': 'new',
}
self.send_mail_bills()
+ if 'BU/PUT' in self.name:
+ self.automatic_reserve_product()
return res
+
+ def automatic_reserve_product(self):
+ if self.state == 'done':
+ po = self.env['purchase.order'].search([
+ ('name', '=', self.group_id.name)
+ ])
+
+ for line in po.order_sales_match_line:
+ if not line.bu_pick:
+ continue
+ line.bu_pick.action_assign()
def check_invoice_date(self):
for picking in self:
diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv
index 3dabae6d..2b970cfd 100755
--- a/indoteknik_custom/security/ir.model.access.csv
+++ b/indoteknik_custom/security/ir.model.access.csv
@@ -182,3 +182,4 @@ access_sale_order_delay,sale.order.delay,model_sale_order_delay,,1,1,1,1
access_production_purchase_match,access.production.purchase.match,model_production_purchase_match,,1,1,1,1
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
diff --git a/indoteknik_custom/views/account_move.xml b/indoteknik_custom/views/account_move.xml
index 0fc62293..2f52b3d9 100644
--- a/indoteknik_custom/views/account_move.xml
+++ b/indoteknik_custom/views/account_move.xml
@@ -37,6 +37,8 @@
</field>
<field name="ref" position="after">
<field name="sale_id" readonly="1" attrs="{'invisible': [('move_type', '!=', 'entry')]}"/>
+ </field>
+ <field name="reklas_misc_id" position="after">
<field name="purchase_order_id" context="{'form_view_ref': 'purchase.purchase_order_form'}" options="{'no_create': True}"/>
<field name="bill_id" readonly="1"/>
</field>
@@ -60,6 +62,7 @@
<field name="due_extension"/>
<field name="counter"/>
<field name="nomor_kwitansi"/>
+ <field name="down_payment"/>
</field>
<field name="to_check" position="after">
<field name="already_paid"/>
@@ -67,6 +70,7 @@
<field name="so_shipping_covered_by"/>
<field name="so_delivery_amt"/>
<field name="flag_delivery_amt"/>
+ <field name="length_of_payment"/>
</field>
<field name="amount_untaxed" position="after">
<field name="other_subtotal" invisible="1"/>
diff --git a/indoteknik_custom/views/approval_payment_term.xml b/indoteknik_custom/views/approval_payment_term.xml
new file mode 100644
index 00000000..87c77385
--- /dev/null
+++ b/indoteknik_custom/views/approval_payment_term.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<odoo>
+ <record id="approval_payment_term_tree" model="ir.ui.view">
+ <field name="name">approval.payment.term.tree</field>
+ <field name="model">approval.payment.term</field>
+ <field name="arch" type="xml">
+ <tree default_order="create_date desc">
+ <field name="number"/>
+ <field name="partner_id"/>
+ <field name="parent_id"/>
+ <field name="property_payment_term_id"/>
+ <field name="approve_date" optional="hide"/>
+ <field name="approve_sales_manager" optional="hide"/>
+ <field name="approve_finance" optional="hide"/>
+ <field name="approve_leader" optional="hide"/>
+ <field name="create_uid" optional="hide"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="approval_payment_term_form" model="ir.ui.view">
+ <field name="name">approval.payment.term.form</field>
+ <field name="model">approval.payment.term</field>
+ <field name="arch" type="xml">
+ <form>
+ <header>
+ <button name="button_approve"
+ string="Approve"
+ type="object"
+ attrs="{'invisible': [('approve_leader', '=', True)]}"
+ />
+ </header>
+ <sheet string="Approval Payment Term">
+ <group>
+ <group>
+ <field name="number" readonly="1"/>
+ <field name="partner_id"/>
+ <field name="parent_id" readonly="1"/>
+ <field name="blocking_stage" attrs="{'readonly': [('number', '=', False)]}"/>
+ <field name="warning_stage" attrs="{'readonly': [('number', '=', False)]}"/>
+ <field name="property_payment_term_id" attrs="{'readonly': [('number', '=', False)]}"/>
+ <field name="active_limit" attrs="{'readonly': [('number', '=', False)]}"/>
+ </group>
+ <group>
+ <field name="reason"/>
+ <field name="approve_date" readonly="1"/>
+ <field name="approve_sales_manager" readonly="1"/>
+ <field name="approve_finance" readonly="1"/>
+ <field name="approve_leader" readonly="1"/>
+ </group>
+ </group>
+ </sheet>
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
+ </form>
+ </field>
+ </record>
+
+ <record id="approval_payment_term_action" model="ir.actions.act_window">
+ <field name="name">Approval Payment Term</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">approval.payment.term</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+ <menuitem id="menu_approval_payment_term" name="Approval Payment Term"
+ parent="account.menu_finance_receivables"
+ action="approval_payment_term_action"
+ sequence="100"
+ />
+
+</odoo> \ No newline at end of file
diff --git a/indoteknik_custom/views/customer_commision.xml b/indoteknik_custom/views/customer_commision.xml
index 4be0840f..1c17bf63 100644
--- a/indoteknik_custom/views/customer_commision.xml
+++ b/indoteknik_custom/views/customer_commision.xml
@@ -70,7 +70,7 @@
statusbar_visible="draft,pengajuan1,pengajuan2,pengajuan3,pengajuan4,approved"
statusbar_colors='{"reject":"red"}'/>
</header>
- <sheet string="Customer Commision">
+ <sheet string="Customer Benefits">
<div class="oe_button_box" name="button_box"/>
<group>
<group>
@@ -83,6 +83,7 @@
<field name="commision_amt_text"/>
<field name="cashback" attrs="{'invisible': [('commision_type', 'not in', ['cashback'])]}"/>
<field name="total_commision" attrs="{'invisible': [('commision_type', 'not in', ['cashback'])]}"/>
+ <field name="total_cashback_text" attrs="{'invisible': [('commision_type', 'not in', ['cashback'])]}"/>
<field name="grouped_so_number" readonly="1"/>
<field name="grouped_invoice_number" readonly="1"/>
<field name="approved_by" readonly="1"/>
@@ -173,7 +174,7 @@
</record>
<record id="customer_commision_action" model="ir.actions.act_window">
- <field name="name">Customer Commision</field>
+ <field name="name">Customer Benefits</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">customer.commision</field>
<field name="search_view_id" ref="view_customer_commision_filter"/>
@@ -181,14 +182,14 @@
</record>
<menuitem id="menu_customer_commision_acct"
- name="Customer Commision"
+ name="Customer Benefits"
action="customer_commision_action"
parent="account.menu_finance_entries"
sequence="113"
/>
<menuitem id="menu_customer_commision_sales"
- name="Customer Commision"
+ name="Customer Benefits"
action="customer_commision_action"
parent="sale.product_menu_catalog"
sequence="101"
diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml
index 97bf40bb..a0f5fc6b 100644
--- a/indoteknik_custom/views/ir_sequence.xml
+++ b/indoteknik_custom/views/ir_sequence.xml
@@ -131,7 +131,7 @@
<field name="number_increment">1</field>
</record>
- <record id="sequence_commision_customer" model="ir.sequence">
+ <!-- <record id="sequence_commision_customer" model="ir.sequence">
<field name="name">Customer Commision</field>
<field name="code">customer.commision</field>
<field name="active">TRUE</field>
@@ -139,6 +139,46 @@
<field name="padding">5</field>
<field name="number_next">1</field>
<field name="number_increment">1</field>
+ </record> -->
+
+ <record id="sequence_commision_cashback" model="ir.sequence">
+ <field name="name">Customer Commision Cashback</field>
+ <field name="code">customer.commision.cashback</field>
+ <field name="prefix">CB/%(year)s/</field>
+ <field name="padding">5</field>
+ <field name="number_next">1</field>
+ <field name="number_increment">1</field>
+ <field name="active">True</field>
+ </record>
+
+ <record id="sequence_approval_payment_term" model="ir.sequence">
+ <field name="name">Approval Payment Term</field>
+ <field name="code">approval.payment.term</field>
+ <field name="prefix">APP/%(year)s/</field>
+ <field name="padding">5</field>
+ <field name="number_next">1</field>
+ <field name="number_increment">1</field>
+ <field name="active">True</field>
+ </record>
+
+ <record id="sequence_commision_fee" model="ir.sequence">
+ <field name="name">Customer Commision Fee</field>
+ <field name="code">customer.commision.fee</field>
+ <field name="prefix">FE/%(year)s/</field>
+ <field name="padding">5</field>
+ <field name="number_next">1</field>
+ <field name="number_increment">1</field>
+ <field name="active">True</field>
+ </record>
+
+ <record id="sequence_commision_rebate" model="ir.sequence">
+ <field name="name">Customer Commision Rebate</field>
+ <field name="code">customer.commision.rebate</field>
+ <field name="prefix">RB/%(year)s/</field>
+ <field name="padding">5</field>
+ <field name="number_next">1</field>
+ <field name="number_increment">1</field>
+ <field name="active">True</field>
</record>
<record id="sequence_automatic_purchase" model="ir.sequence">
diff --git a/indoteknik_custom/views/mrp_production.xml b/indoteknik_custom/views/mrp_production.xml
index 3de52a08..5057415f 100644
--- a/indoteknik_custom/views/mrp_production.xml
+++ b/indoteknik_custom/views/mrp_production.xml
@@ -11,7 +11,7 @@
<field name="bom_id" position="after">
<field name="desc"/>
<field name="sale_order"/>
- <field name="is_po"/>
+ <field name="is_po" readonly="1"/>
</field>
<xpath expr="//form/sheet/notebook/page/field[@name='move_raw_ids']/tree/field[@name='product_uom_qty']" position="before">
<field name="vendor_id"/>
diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml
index 7b568d07..dae23eed 100755
--- a/indoteknik_custom/views/purchase_order.xml
+++ b/indoteknik_custom/views/purchase_order.xml
@@ -87,7 +87,8 @@
<field name="payment_term_id"/>
<field name="total_cost_service" attrs="{'required': [('partner_id', 'in', [9688, 29712])]}"/>
<field name="total_delivery_amt" attrs="{'required': [('partner_id', 'in', [9688, 29712])]}"/>
- <field name="product_bom_id"/>
+ <field name="product_bom_id" attrs="{'invisible': [('product_bom_id', '=', None)]}"/>
+ <field name="manufacturing_id" attrs="{'invisible': [('product_bom_id', '=', None)]}"/>
<!-- <field name="move_id" domain="[('move_type','=','entry')]" context="{'form_view_ref': 'account.view_move_form'}" options="{'no_create': True}"/> -->
</field>
<field name="amount_total" position="after">
@@ -332,6 +333,7 @@
<field name="delivery_amt" optional="hide"/>
<field name="margin_deduct" optional="hide"/>
<field name="hold_outgoing_so" optional="hide"/>
+ <field name="bu_pick" optional="hide"/>
<field name="margin_so"/>
</tree>
</field>
diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml
index 30b5ca36..ac4d0364 100644
--- a/indoteknik_custom/views/res_partner.xml
+++ b/indoteknik_custom/views/res_partner.xml
@@ -108,6 +108,13 @@
<xpath expr="//field[@name='property_supplier_payment_term_id']" position="attributes">
<attribute name="readonly">1</attribute>
</xpath>
+ <xpath expr="//notebook/page[@name='accounting']" position="inside">
+ <group string="Aging Info">
+ <field name="avg_aging" readonly="1"/>
+ <field name="payment_difficulty" attrs="{'readonly': [('parent_id', '!=', False)]}" />
+ <field name="payment_history_url" readonly="1" />
+ </group>
+ </xpath>
<notebook>
<page string="Pengajuan Tempo">
<!-- Informasi Usaha Section -->
@@ -181,6 +188,11 @@
<field name="dokumen_pengiriman_input"/>
<field name="dokumen_invoice"/>
</group>
+ <!-- <group string="Aging Info">
+ <field name="avg_aging" readonly="1"/>
+ <field name="payment_difficulty" attrs="{'readonly': [('parent_id', '!=', False)]}" />
+ <field name="payment_history_url" readonly="1" />
+ </group> -->
</group>
<!-- Supplier Lines Section -->