summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIndoteknik . <it@fixcomart.co.id>2025-05-26 11:25:15 +0700
committerIndoteknik . <it@fixcomart.co.id>2025-05-26 11:25:15 +0700
commit7d7a6d4421a664e2ddd9308b15ea9f9e5e54c4a9 (patch)
treefd8b1877af11acdbbb627f03ea5b7189281a009b
parentbab061bc003f132e738d7ad2f9d99df903392d1a (diff)
parentc1aefea6e72798848d090abb32bb753c550ce76b (diff)
(andri) resolved conflict
-rwxr-xr-xindoteknik_custom/__manifest__.py1
-rw-r--r--indoteknik_custom/models/account_move.py13
-rw-r--r--indoteknik_custom/models/account_move_due_extension.py2
-rw-r--r--indoteknik_custom/models/approval_invoice_date.py3
-rw-r--r--indoteknik_custom/models/automatic_purchase.py99
-rw-r--r--indoteknik_custom/models/barcoding_product.py34
-rw-r--r--indoteknik_custom/models/commision.py62
-rw-r--r--indoteknik_custom/models/coretax_fatur.py88
-rw-r--r--indoteknik_custom/models/mrp_production.py36
-rwxr-xr-xindoteknik_custom/models/product_template.py43
-rwxr-xr-xindoteknik_custom/models/purchase_order_line.py3
-rw-r--r--indoteknik_custom/models/purchase_order_sales_match.py1
-rwxr-xr-xindoteknik_custom/models/purchase_pricelist.py11
-rw-r--r--indoteknik_custom/models/purchasing_job_multi_update.py2
-rw-r--r--indoteknik_custom/models/requisition.py14
-rwxr-xr-xindoteknik_custom/models/sale_order.py607
-rw-r--r--indoteknik_custom/models/sale_order_line.py67
-rw-r--r--indoteknik_custom/models/shipment_group.py42
-rw-r--r--indoteknik_custom/models/solr/product_template.py2
-rw-r--r--indoteknik_custom/models/stock_move.py1
-rw-r--r--indoteknik_custom/models/stock_picking.py104
-rw-r--r--indoteknik_custom/models/user_pengajuan_tempo_request.py6
-rw-r--r--indoteknik_custom/models/voucher.py258
-rw-r--r--indoteknik_custom/models/website_user_cart.py121
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv1
-rw-r--r--indoteknik_custom/views/account_move.xml1
-rw-r--r--indoteknik_custom/views/barcoding_product.xml5
-rw-r--r--indoteknik_custom/views/customer_commision.xml234
-rw-r--r--indoteknik_custom/views/ir_sequence.xml2
-rw-r--r--indoteknik_custom/views/product_product.xml1
-rw-r--r--indoteknik_custom/views/project_views.xml13
-rwxr-xr-xindoteknik_custom/views/purchase_order.xml2
-rwxr-xr-xindoteknik_custom/views/sale_order.xml495
-rw-r--r--indoteknik_custom/views/shipment_group.xml9
-rw-r--r--indoteknik_custom/views/stock_picking.xml12
-rw-r--r--indoteknik_custom/views/user_pengajuan_tempo_request.xml2
-rwxr-xr-xindoteknik_custom/views/voucher.xml74
37 files changed, 1588 insertions, 883 deletions
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py
index 8272efc4..9fe3dcdb 100755
--- a/indoteknik_custom/__manifest__.py
+++ b/indoteknik_custom/__manifest__.py
@@ -155,6 +155,7 @@
'views/user_pengajuan_tempo.xml',
'views/stock_backorder_confirmation_views.xml',
'views/barcoding_product.xml',
+ 'views/project_views.xml',
'report/report.xml',
'report/report_banner_banner.xml',
'report/report_banner_banner2.xml',
diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py
index 906985de..30de67be 100644
--- a/indoteknik_custom/models/account_move.py
+++ b/indoteknik_custom/models/account_move.py
@@ -66,6 +66,19 @@ class AccountMove(models.Model):
other_taxes = fields.Float(string="Other Taxes", compute='compute_other_taxes')
is_hr = fields.Boolean(string="Is HR?", default=False)
purchase_order_id = fields.Many2one('purchase.order', string='Purchase Order')
+ length_of_payment = fields.Integer(string="Length of Payment", compute='compute_length_of_payment')
+
+ 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 _update_line_name_from_ref(self):
"""Update all account.move.line name fields with ref from account.move"""
diff --git a/indoteknik_custom/models/account_move_due_extension.py b/indoteknik_custom/models/account_move_due_extension.py
index c48c2372..4a3f40e2 100644
--- a/indoteknik_custom/models/account_move_due_extension.py
+++ b/indoteknik_custom/models/account_move_due_extension.py
@@ -96,7 +96,7 @@ class DueExtension(models.Model):
sales = self.env['sale.order'].browse(self.order_id.id)
- sales.action_confirm()
+ sales.with_context({'due_approve': True}).action_confirm()
self.order_id.due_id = self.id
self.approve_by = self.env.user.id
self.date_approve = datetime.utcnow()
diff --git a/indoteknik_custom/models/approval_invoice_date.py b/indoteknik_custom/models/approval_invoice_date.py
index 48546e55..e1bc4c9b 100644
--- a/indoteknik_custom/models/approval_invoice_date.py
+++ b/indoteknik_custom/models/approval_invoice_date.py
@@ -31,7 +31,8 @@ class ApprovalInvoiceDate(models.Model):
def button_approve(self):
if not self.env.user.is_accounting:
raise UserError("Hanya Accounting Yang Bisa Approve")
- self.move_id.invoice_date = self.date_doc_do
+ self.move_id.invoice_date = self.date_doc_do.date()
+ self.picking_id.date_doc_kirim = self.date_doc_do
self.state = 'done'
self.approve_date = datetime.utcnow()
self.approve_by = self.env.user.id
diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py
index fbdf8dae..c9edf07c 100644
--- a/indoteknik_custom/models/automatic_purchase.py
+++ b/indoteknik_custom/models/automatic_purchase.py
@@ -1,4 +1,4 @@
-from odoo import models, api, fields
+from odoo import models, api, fields, tools
from odoo.exceptions import UserError
from datetime import datetime
import logging, math
@@ -67,6 +67,15 @@ class AutomaticPurchase(models.Model):
if count > 0:
raise UserError('Ada sekitar %s SO Yang sudah create PO, berikut SO nya: %s' % (count, ', '.join(names)))
+
+ def unlink_note_pj(self):
+ product = self.purchase_lines.mapped('product_id')
+ pj_state = self.env['purchasing.job.state'].search([
+ ('purchasing_job_id', 'in', product.ids)
+ ])
+
+ for line in pj_state:
+ line.unlink()
def create_po_from_automatic_purchase(self):
if not self.purchase_lines:
@@ -75,6 +84,7 @@ class AutomaticPurchase(models.Model):
raise UserError('Sudah pernah di create PO')
current_time = datetime.now()
+ self.unlink_note_pj()
vendor_ids = self.env['automatic.purchase.line'].read_group(
[('automatic_purchase_id', '=', self.id), ('partner_id', '!=', False)],
fields=['partner_id'],
@@ -284,7 +294,7 @@ class AutomaticPurchase(models.Model):
def create_purchase_order_sales_match(self, purchase_order):
matches_so_product_ids = [line.product_id.id for line in purchase_order.order_line]
- matches_so = self.env['automatic.purchase.sales.match'].search([
+ matches_so = self.env['v.sale.notin.matchpo'].search([
('automatic_purchase_id', '=', self.id),
('sale_line_id.product_id', 'in', matches_so_product_ids),
])
@@ -292,6 +302,8 @@ class AutomaticPurchase(models.Model):
sale_ids_set = set()
sale_ids_name = set()
for sale_order in matches_so:
+ # @stephan skip so line yang sudah pernah ada di purchase order sales match sebelumnya
+
salesperson_name = sale_order.sale_id.user_id.name
sale_id_with_salesperson = f"{sale_order.sale_id.name} - {salesperson_name}"
@@ -474,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_available_bandengan
+ qty_purchase = point.product_max_qty - point.product_id.qty_incoming_bandengan - point.product_id.qty_onhand_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:
@@ -577,18 +589,18 @@ class AutomaticPurchaseLine(models.Model):
def _get_valid_purchase_price(self, purchase_price):
price = 0
- taxes = ''
+ taxes = 24
human_last_update = purchase_price.human_last_update or datetime.min
system_last_update = purchase_price.system_last_update or datetime.min
- if purchase_price.taxes_product_id.type_tax_use == 'purchase':
- price = purchase_price.product_price
- taxes = purchase_price.taxes_product_id.id
+ #if purchase_price.taxes_product_id.type_tax_use == 'purchase':
+ price = purchase_price.product_price
+ taxes = purchase_price.taxes_product_id.id or 24
if system_last_update > human_last_update:
- if purchase_price.taxes_system_id.type_tax_use == 'purchase':
- price = purchase_price.system_price
- taxes = purchase_price.taxes_system_id.id
+ #if purchase_price.taxes_system_id.type_tax_use == 'purchase':
+ price = purchase_price.system_price
+ taxes = purchase_price.taxes_system_id.id or 24
return price, taxes
@@ -655,3 +667,70 @@ class SyncPurchasingJob(models.Model):
outgoing = fields.Float(string="Outgoing")
action = fields.Char(string="Status")
date = fields.Datetime(string="Date Sync")
+
+
+class SaleNotInMatchPO(models.Model):
+ # created by @stephan for speed up performance while create po from automatic purchase
+ _name = 'v.sale.notin.matchpo'
+ _auto = False
+ _rec_name = 'id'
+
+ id = fields.Integer()
+ automatic_purchase_id = fields.Many2one('automatic.purchase', string='APO')
+ automatic_purchase_line_id = fields.Many2one('automatic.purchase.line', string='APO Line')
+ sale_id = fields.Many2one('sale.order', string='Sale')
+ sale_line_id = fields.Many2one('sale.order.line', string='Sale Line')
+ picking_id = fields.Many2one('stock.picking', string='Picking')
+ move_id = fields.Many2one('stock.move', string='Move')
+ partner_id = fields.Many2one('res.partner', string='Partner')
+ partner_invoice_id = fields.Many2one('res.partner', string='Partner Invoice')
+ salesperson_id = fields.Many2one('res.user', string='Salesperson')
+ product_id = fields.Many2one('product.product', string='Product')
+ qty_so = fields.Float(string='Qty SO')
+ qty_po = fields.Float(string='Qty PO')
+ create_uid = fields.Many2one('res.user', string='Created By')
+ create_date = fields.Datetime(string='Create Date')
+ write_uid = fields.Many2one('res.user', string='Updated By')
+ write_date = fields.Many2one(string='Updated')
+ purchase_price = fields.Many2one(string='Purchase Price')
+ purchase_tax_id = fields.Many2one('account.tax', string='Purchase Tax')
+ note_procurement = fields.Many2one(string='Note Procurement')
+
+ # 1. yang bug
+ # def init(self):
+ # tools.drop_view_if_exists(self.env.cr, self._table)
+ # self.env.cr.execute("""
+ # CREATE OR REPLACE VIEW %s AS(
+ # select apsm.id, apsm.automatic_purchase_id, apsm.automatic_purchase_line_id, apsm.sale_id, apsm.sale_line_id,
+ # apsm.picking_id, apsm.move_id, apsm.partner_id,
+ # apsm.partner_invoice_id, apsm.salesperson_id, apsm.product_id, apsm.qty_so, apsm.qty_po, apsm.create_uid,
+ # apsm.create_date, apsm.write_uid, apsm.write_date, apsm.purchase_price,
+ # apsm.purchase_tax_id, apsm.note_procurement
+ # from automatic_purchase_sales_match apsm
+ # where apsm.sale_line_id not in (
+ # select distinct coalesce(posm.sale_line_id,0)
+ # from purchase_order_sales_match posm
+ # join purchase_order po on po.id = posm.purchase_order_id
+ # where po.state not in ('cancel')
+ # )
+ # )
+ # """ % self._table)
+
+ def init(self):
+ tools.drop_view_if_exists(self.env.cr, self._table)
+ self.env.cr.execute("""
+ CREATE OR REPLACE VIEW %s AS(
+ select apsm.id, apsm.automatic_purchase_id, apsm.automatic_purchase_line_id, apsm.sale_id, apsm.sale_line_id,
+ apsm.picking_id, apsm.move_id, apsm.partner_id,
+ apsm.partner_invoice_id, apsm.salesperson_id, apsm.product_id, apsm.qty_so, apsm.qty_po, apsm.create_uid,
+ apsm.create_date, apsm.write_uid, apsm.write_date, apsm.purchase_price,
+ apsm.purchase_tax_id, apsm.note_procurement
+ from automatic_purchase_sales_match apsm
+ where apsm.sale_line_id not in (
+ select distinct coalesce(posm.sale_line_id,0)
+ from purchase_order_sales_match posm
+ join purchase_order po on po.id = posm.purchase_order_id
+ where po.state not in ('cancel')
+ )
+ )
+ """ % self._table) \ No newline at end of file
diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py
index 204c6128..335b481a 100644
--- a/indoteknik_custom/models/barcoding_product.py
+++ b/indoteknik_custom/models/barcoding_product.py
@@ -12,7 +12,7 @@ class BarcodingProduct(models.Model):
barcoding_product_line = fields.One2many('barcoding.product.line', 'barcoding_product_id', string='Barcoding Product Lines', auto_join=True)
product_id = fields.Many2one('product.product', string="Product", tracking=3)
quantity = fields.Float(string="Quantity", tracking=3)
- type = fields.Selection([('print', 'Print Barcode'), ('barcoding', 'Add Barcode To Product'), ('barcoding_box', 'Add Barcode Box To Product')], string='Type', default='print')
+ type = fields.Selection([('print', 'Print Barcode'), ('barcoding', 'Add Barcode To Product'), ('barcoding_box', 'Add Barcode Box To Product'), ('multiparts', 'Multiparts Product')], string='Type', default='print')
barcode = fields.Char(string="Barcode")
qty_pcs_box = fields.Char(string="Quantity Pcs Box")
@@ -40,16 +40,29 @@ class BarcodingProduct(models.Model):
@api.onchange('product_id', 'quantity')
def _onchange_product_or_quantity(self):
- """Update barcoding_product_line based on product_id and quantity"""
if self.product_id and self.quantity > 0:
- # Clear existing lines
self.barcoding_product_line = [(5, 0, 0)]
- # Add a new line with the current product and quantity
- self.barcoding_product_line = [(0, 0, {
- 'product_id': self.product_id.id,
- 'barcoding_product_id': self.id,
- }) for _ in range(int(self.quantity))]
+ lines = []
+ for i in range(int(self.quantity)):
+ lines.append((0, 0, {
+ 'product_id': self.product_id.id,
+ 'barcoding_product_id': self.id,
+ 'sequence_with_total': f"{i+1}/{int(self.quantity)}"
+ }))
+ self.barcoding_product_line = lines
+
+ def write(self, vals):
+ res = super().write(vals)
+ if 'quantity' in vals and self.type == 'multiparts':
+ self._update_sequence_with_total()
+ return res
+
+ def _update_sequence_with_total(self):
+ for rec in self:
+ total = int(rec.quantity)
+ for index, line in enumerate(rec.barcoding_product_line, start=1):
+ line.sequence_with_total = f"{index}/{total}"
class BarcodingProductLine(models.Model):
@@ -59,4 +72,7 @@ class BarcodingProductLine(models.Model):
barcoding_product_id = fields.Many2one('barcoding.product', string='Barcoding Product Ref', required=True, ondelete='cascade', index=True, copy=False)
product_id = fields.Many2one('product.product', string="Product")
- qr_code_variant = fields.Binary("QR Code Variant", related='product_id.qr_code_variant') \ No newline at end of file
+ qr_code_variant = fields.Binary("QR Code Variant", related='product_id.qr_code_variant')
+ sequence_with_total = fields.Char(
+ string="Sequence"
+ ) \ No newline at end of file
diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py
index 32e81b9a..eeaa8efc 100644
--- a/indoteknik_custom/models/commision.py
+++ b/indoteknik_custom/models/commision.py
@@ -1,8 +1,10 @@
from odoo import models, api, fields, _
from odoo.exceptions import UserError
from datetime import datetime
+# import datetime
import logging
from terbilang import Terbilang
+import pytz
_logger = logging.getLogger(__name__)
@@ -199,7 +201,8 @@ class CustomerCommision(models.Model):
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)])
+ domain=lambda self: [
+ ('groups_id', 'in', self.env.ref('sales_team.group_sale_salesman').id)])
date_approved_sales = fields.Datetime(string="Date Approved Sales", tracking=True)
date_approved_marketing = fields.Datetime(string="Date Approved Marketing", tracking=True)
@@ -211,6 +214,46 @@ class CustomerCommision(models.Model):
position_pimpinan = fields.Char(string="Position Pimpinan", tracking=True)
position_accounting = fields.Char(string="Position Accounting", tracking=True)
+ # get partner ids so it can be grouped by
+ @api.model
+ def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
+ if 'partner_ids' in groupby:
+ # Get all records matching the domain
+ records = self.search(domain)
+
+ # Create groups for each partner
+ groups = {}
+ for record in records:
+ for partner in record.partner_ids:
+ if partner.id not in groups:
+ groups[partner.id] = {
+ 'partner_ids': partner,
+ 'records': self.env['customer.commision']
+ }
+ groups[partner.id]['records'] |= record
+
+ # Format the result
+ result = []
+ for partner_id, group_data in groups.items():
+ partner = group_data['partner_ids']
+ record_ids = group_data['records'].ids
+
+ # Create the domain
+ group_domain = [('id', 'in', record_ids)]
+ if domain:
+ group_domain = ['&'] + domain + group_domain
+
+ result.append({
+ 'partner_ids': (partner.id, partner.display_name),
+ 'partner_ids_count': len(record_ids),
+ '__domain': group_domain,
+ '__count': len(record_ids),
+ })
+
+ return result
+
+ return super(CustomerCommision, self).read_group(domain, fields, groupby, offset, limit, orderby, lazy)
+
def compute_delivery_amt_text(self):
tb = Terbilang()
@@ -276,6 +319,9 @@ class CustomerCommision(models.Model):
@api.constrains('commision_amt')
def _onchange_commision_amt(self):
+ """
+ Constrain to update commision percent from commision amount
+ """
if not self.env.context.get('_onchange_commision_amt', True):
return
@@ -301,30 +347,34 @@ class CustomerCommision(models.Model):
return result
def action_confirm_customer_commision(self):
- now = datetime.utcnow()
+ jakarta_tz = pytz.timezone('Asia/Jakarta')
+ now = datetime.now(jakarta_tz)
+
+ now_naive = now.replace(tzinfo=None)
+
if not self.status or self.status == 'draft':
self.status = 'pengajuan1'
elif self.status == 'pengajuan1' and self.env.user.is_sales_manager:
self.status = 'pengajuan2'
self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name
- self.date_approved_sales = now
+ self.date_approved_sales = now_naive
self.position_sales = 'Sales Manager'
elif self.status == 'pengajuan2' and self.env.user.id == 19:
self.status = 'pengajuan3'
self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name
- self.date_approved_marketing = now
+ self.date_approved_marketing = now_naive
self.position_marketing = 'Marketing Manager'
elif self.status == 'pengajuan3' and self.env.user.is_leader:
self.status = 'pengajuan4'
self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name
- self.date_approved_pimpinan = now
+ self.date_approved_pimpinan = now_naive
self.position_pimpinan = 'Pimpinan'
elif self.status == 'pengajuan4' and self.env.user.id == 1272:
for line in self.commision_lines:
line.invoice_id.is_customer_commision = True
self.status = 'approved'
self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name
- self.date_approved_accounting = now
+ self.date_approved_accounting = now_naive
self.position_accounting = 'Accounting'
else:
raise UserError('Harus di approved oleh yang bersangkutan')
diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py
index b4bffbd2..92ff1a72 100644
--- a/indoteknik_custom/models/coretax_fatur.py
+++ b/indoteknik_custom/models/coretax_fatur.py
@@ -4,19 +4,23 @@ from xml.dom import minidom
import base64
import re
+
class CoretaxFaktur(models.Model):
_name = 'coretax.faktur'
_description = 'Export Faktur ke XML'
-
- export_file = fields.Binary(string="Export File", )
- export_filename = fields.Char(string="Export File", )
-
+
+ export_file = fields.Binary(string="Export File", )
+ export_filename = fields.Char(string="Export File", )
+
+ DISCOUNT_ACCOUNT_ID = 463
+
def validate_and_format_number(slef, input_number):
# Hapus semua karakter non-digit
cleaned_number = re.sub(r'\D', '', input_number)
-
+
total_sum = sum(int(char) for char in cleaned_number)
- if total_sum == 0 :
+
+ if total_sum == 0:
return '0000000000000000'
# Hitung jumlah digit
@@ -39,22 +43,16 @@ class CoretaxFaktur(models.Model):
# Tambahkan elemen ListOfTaxInvoice
list_of_tax_invoice = ET.SubElement(root, 'ListOfTaxInvoice')
- # Dapatkan data faktur
- # inv_obj = self.env['account.move']
- # invoices = inv_obj.search([('is_efaktur_exported','=',True),
- # ('state','=','posted'),
- # ('efaktur_id','!=', False),
- # ('move_type','=','out_invoice')], limit = 5)
-
for invoice in invoices:
tax_invoice = ET.SubElement(list_of_tax_invoice, 'TaxInvoice')
buyerTIN = self.validate_and_format_number(invoice.partner_id.npwp)
nitku = invoice.partner_id.nitku
formula = nitku if nitku else buyerTIN.ljust(len(buyerTIN) + 6, '0')
- buyerIDTKU = formula if sum(int(char) for char in buyerTIN) > 0 else '000000'
+ buyerIDTKU = formula if sum(int(char) for char in buyerTIN) > 0 else '000000'
# Tambahkan elemen faktur
- ET.SubElement(tax_invoice, 'TaxInvoiceDate').text = invoice.invoice_date.strftime('%Y-%m-%d') if invoice.invoice_date else ''
+ ET.SubElement(tax_invoice, 'TaxInvoiceDate').text = invoice.invoice_date.strftime(
+ '%Y-%m-%d') if invoice.invoice_date else ''
ET.SubElement(tax_invoice, 'TaxInvoiceOpt').text = 'Normal'
ET.SubElement(tax_invoice, 'TrxCode').text = '04'
ET.SubElement(tax_invoice, 'AddInfo')
@@ -64,30 +62,72 @@ class CoretaxFaktur(models.Model):
ET.SubElement(tax_invoice, 'FacilityStamp')
ET.SubElement(tax_invoice, 'SellerIDTKU').text = '0742260227086000000000'
ET.SubElement(tax_invoice, 'BuyerTin').text = buyerTIN
- ET.SubElement(tax_invoice, 'BuyerDocument').text = 'TIN' if sum(int(char) for char in buyerTIN) > 0 else 'Other ID'
+ ET.SubElement(tax_invoice, 'BuyerDocument').text = 'TIN' if sum(
+ int(char) for char in buyerTIN) > 0 else 'Other ID'
ET.SubElement(tax_invoice, 'BuyerCountry').text = 'IDN'
- ET.SubElement(tax_invoice, 'BuyerDocumentNumber').text = '-' if sum(int(char) for char in buyerTIN) > 0 else str(invoice.partner_id.id)
+ ET.SubElement(tax_invoice, 'BuyerDocumentNumber').text = '-' if sum(
+ int(char) for char in buyerTIN) > 0 else str(invoice.partner_id.id)
ET.SubElement(tax_invoice, 'BuyerName').text = invoice.partner_id.nama_wajib_pajak or ''
ET.SubElement(tax_invoice, 'BuyerAdress').text = invoice.partner_id.alamat_lengkap_text or ''
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
+ )
+
+ # Filter discount
+ 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)
+ )
+ )
+
+ # Calculate total product amount (before discount)
+ total_product_amount = sum(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 invoice.invoice_line_ids:
- otherTaxBase = round(line.price_subtotal * (11/12)) if line.price_subtotal else 0
+
+ for line in product_lines:
+ # Calculate prorated discount
+ 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
+ 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)
+
+ # 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, 'Unit').text = 'UM.0018'
- ET.SubElement(good_service, 'Price').text = str(round(line.price_subtotal/line.quantity, 2)) if line.price_subtotal else '0'
- ET.SubElement(good_service, 'Qty').text = str(line.quantity)
- ET.SubElement(good_service, 'TotalDiscount').text = '0'
- ET.SubElement(good_service, 'TaxBase').text = str(round(line.price_subtotal)) if line.price_subtotal else '0'
+ ET.SubElement(good_service, 'Price').text = str(round(subtotal / quantity, 2)) if subtotal else '0'
+ 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, 'OtherTaxBase').text = str(otherTaxBase)
ET.SubElement(good_service, 'VATRate').text = '12'
- ET.SubElement(good_service, 'VAT').text = str(round(otherTaxBase * 0.12, 2))
+ ET.SubElement(good_service, 'VAT').text = str(vat_amount)
ET.SubElement(good_service, 'STLGRate').text = '0'
ET.SubElement(good_service, 'STLG').text = '0'
diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py
index 8179fe56..14821f27 100644
--- a/indoteknik_custom/models/mrp_production.py
+++ b/indoteknik_custom/models/mrp_production.py
@@ -180,29 +180,29 @@ class MrpProduction(models.Model):
# delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S')
price = 0
- taxes = ''
+ taxes = 24
vendor_id = ''
human_last_update = purchase_price.human_last_update or datetime.min
system_last_update = purchase_price.system_last_update or datetime.min
- if purchase_price.taxes_product_id.type_tax_use == 'purchase':
- price = purchase_price.product_price
- taxes = purchase_price.taxes_product_id.id
+ #if purchase_price.taxes_product_id.type_tax_use == 'purchase':
+ price = purchase_price.product_price
+ taxes = purchase_price.taxes_product_id.id or 24
+ vendor_id = purchase_price.vendor_id.id
+ if delta_time > human_last_update:
+ price = 0
+ taxes = ''
+ vendor_id = ''
+
+ if system_last_update > human_last_update:
+ #if purchase_price.taxes_system_id.type_tax_use == 'purchase':
+ price = purchase_price.system_price
+ taxes = purchase_price.taxes_system_id.id or 24
vendor_id = purchase_price.vendor_id.id
- if delta_time > human_last_update:
+ if delta_time > system_last_update:
price = 0
- taxes = ''
+ taxes = 24
vendor_id = ''
-
- if system_last_update > human_last_update:
- if purchase_price.taxes_system_id.type_tax_use == 'purchase':
- price = purchase_price.system_price
- taxes = purchase_price.taxes_system_id.id
- vendor_id = purchase_price.vendor_id.id
- if delta_time > system_last_update:
- price = 0
- taxes = ''
- vendor_id = ''
return price, taxes, vendor_id
@@ -244,6 +244,10 @@ 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']:
+ raise UserError((
+ "SO harus diconfirm terlebih dahulu."
+ ))
if not record.production_id or not record.product_id:
continue
diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py
index 5480204f..3bb54f44 100755
--- a/indoteknik_custom/models/product_template.py
+++ b/indoteknik_custom/models/product_template.py
@@ -909,7 +909,8 @@ class ProductProduct(models.Model):
qty_onhand_bandengan = fields.Float(string='Onhand BU', compute='_get_qty_onhand_bandengan')
clean_website_description = fields.Char(string='Clean Website Description', compute='_get_clean_website_description')
qty_incoming_bandengan = fields.Float(string='Incoming BU', compute='_get_qty_incoming_bandengan')
- qty_outgoing_bandengan = fields.Float(string='Outgoing BU', compute='_get_qty_outgoing_bandengan')
+ qty_outgoing_bandengan = fields.Float(string='Outgoing BU', compute='_get_qty_outgoing_bandengan', help='only outgoing from sales order bandengan')
+ qty_outgoing_mo_bandengan = fields.Float(string='Outgoing MO BU', compute='_get_qty_outgoing_mo_bandengan', help='only outgoing from manufacturing order bandengan')
qty_available_bandengan = fields.Float(string='Available BU', compute='_get_qty_available_bandengan')
qty_free_bandengan = fields.Float(string='Free BU', compute='_get_qty_free_bandengan')
qty_upcoming = fields.Float(string='Qty Upcoming', compute='_get_qty_upcoming')
@@ -1111,12 +1112,24 @@ class ProductProduct(models.Model):
domain=[
('product_id', '=', product.id),
('location_id', 'in', [57, 83]),
+ ('mo_id', '=', False),
+ ('hold_outgoing', '=', False)
],
fields=['qty_need'],
groupby=[]
)[0].get('qty_need', 0.0)
product.qty_outgoing_bandengan = qty
+ def _get_qty_outgoing_mo_bandengan(self):
+ for product in self:
+ records = self.env['v.move.outstanding'].search([
+ ('product_id.id', '=', product.id),
+ ('location_id.id', 'in', [57, 83]),
+ ('mo_id.id', '>', 0)
+ ])
+ qty = sum(records.mapped('qty_need') or [0.0])
+ product.qty_outgoing_mo_bandengan = qty
+
def _get_qty_onhand_bandengan(self):
for product in self:
qty_onhand = self.env['stock.quant'].search([
@@ -1128,7 +1141,7 @@ class ProductProduct(models.Model):
def _get_qty_available_bandengan(self):
for product in self:
- qty_available = product.qty_incoming_bandengan + product.qty_onhand_bandengan - product.qty_outgoing_bandengan
+ qty_available = product.qty_incoming_bandengan + product.qty_onhand_bandengan - product.qty_outgoing_bandengan - product.qty_outgoing_mo_bandengan
product.qty_available_bandengan = qty_available or 0
def _get_qty_free_bandengan(self):
@@ -1292,10 +1305,20 @@ class ProductProduct(models.Model):
# simpan data lama dan log perubahan field
def write(self, vals):
- old_values = self._collect_old_values(vals)
- result = super().write(vals)
- self._log_field_changes_product_variants(vals, old_values)
- return result
+ tracked_fields = [
+ 'default_code', 'name', 'weight', 'x_manufacture',
+ 'public_categ_ids', 'search_rank', 'search_rank_weekly',
+ 'image_1920', 'unpublished', 'image_carousel_lines'
+ ]
+
+ if any(field in vals for field in tracked_fields):
+ old_values = self._collect_old_values(vals)
+ result = super().write(vals)
+ self._log_field_changes_product_variants(vals, old_values)
+ return result
+ else:
+ return super().write(vals)
+
class OutstandingMove(models.Model):
_name = 'v.move.outstanding'
@@ -1308,6 +1331,8 @@ class OutstandingMove(models.Model):
qty_need = fields.Float(string='Qty Need', help='Qty yang akan outgoing / incoming')
location_id = fields.Many2one('stock.location', string='Location', help='Lokasi asal')
location_dest_id = fields.Many2one('stock.location', string='Location To', help='Lokasi tujuan')
+ mo_id = fields.Many2one('mrp.production', string='Manufacturing Order')
+ hold_outgoing = fields.Boolean(string='Hold Outgoing')
def init(self):
# where clause 'state in' follow the origin of outgoing and incoming odoo
@@ -1316,8 +1341,12 @@ class OutstandingMove(models.Model):
CREATE OR REPLACE VIEW %s AS
select sm.id, sm.reference, sm.product_id,
sm.product_uom_qty as qty_need,
- sm.location_id, sm.location_dest_id
+ sm.location_id, sm.location_dest_id,
+ sm.raw_material_production_id as mo_id,
+ so.hold_outgoing
from stock_move sm
+ left join procurement_group pg on pg.id = sm.group_id
+ left join sale_order so on so.id = pg.sale_id
where 1=1
and sm.state in(
'waiting',
diff --git a/indoteknik_custom/models/purchase_order_line.py b/indoteknik_custom/models/purchase_order_line.py
index 033469b8..315795d5 100755
--- a/indoteknik_custom/models/purchase_order_line.py
+++ b/indoteknik_custom/models/purchase_order_line.py
@@ -385,3 +385,6 @@ class PurchaseOrderLine(models.Model):
line.delivery_amt_line = delivery_amt * contribution
else:
line.delivery_amt_line = 0
+
+
+
diff --git a/indoteknik_custom/models/purchase_order_sales_match.py b/indoteknik_custom/models/purchase_order_sales_match.py
index ed013dd5..0bd0092b 100644
--- a/indoteknik_custom/models/purchase_order_sales_match.py
+++ b/indoteknik_custom/models/purchase_order_sales_match.py
@@ -27,6 +27,7 @@ class PurchaseOrderSalesMatch(models.Model):
purchase_price_so = fields.Float(string='Purchase Price Sale Order', related='sale_line_id.purchase_price')
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')
def _compute_purchase_line_id(self):
for line in self:
diff --git a/indoteknik_custom/models/purchase_pricelist.py b/indoteknik_custom/models/purchase_pricelist.py
index 764911cf..b3a473b6 100755
--- a/indoteknik_custom/models/purchase_pricelist.py
+++ b/indoteknik_custom/models/purchase_pricelist.py
@@ -84,6 +84,15 @@ class PurchasePricelist(models.Model):
massage="Ada duplikat product dan vendor, berikut data yang anda duplikat : \n" + str(existing_purchase.product_id.name) + " - " + str(existing_purchase.vendor_id.name) + " - " + str(existing_purchase.product_price)
if existing_purchase:
raise UserError(massage)
+
+ def sync_pricelist_item_promo(self, product):
+ pricelist_product = self.env['product.pricelist.item'].search([('product_id', '=', product.id), ('pricelist_id', '=', 17022)])
+ for pricelist in pricelist_product:
+ if pricelist.fixed_price == 0:
+ flashsale = self.env['product.pricelist.item'].search([('product_id', '=', product.id), ('pricelist_id.is_flash_sale', '=', True)])
+ if flashsale:
+ flashsale.fixed_price = 0
+ return
def action_calculate_pricelist(self):
MAX_PRICELIST = 10
@@ -95,6 +104,8 @@ class PurchasePricelist(models.Model):
records = self.env['purchase.pricelist'].browse(active_ids)
price_group = self.env['price.group'].collect_price_group()
for rec in records:
+ if rec.include_price == 0:
+ rec.sync_pricelist_item_promo(rec.product_id)
product_group = rec.product_id.product_tmpl_id.x_manufacture.pricing_group or None
price_incl = rec.include_price
diff --git a/indoteknik_custom/models/purchasing_job_multi_update.py b/indoteknik_custom/models/purchasing_job_multi_update.py
index deba960a..80a43e45 100644
--- a/indoteknik_custom/models/purchasing_job_multi_update.py
+++ b/indoteknik_custom/models/purchasing_job_multi_update.py
@@ -18,7 +18,7 @@ class PurchasingJobMultiUpdate(models.TransientModel):
('purchasing_job_id', '=', product.id)
])
- purchasing_job_state.unlink()
+ # purchasing_job_state.unlink()
purchasing_job_state.create({
'purchasing_job_id': product.id,
diff --git a/indoteknik_custom/models/requisition.py b/indoteknik_custom/models/requisition.py
index 1d350929..74236850 100644
--- a/indoteknik_custom/models/requisition.py
+++ b/indoteknik_custom/models/requisition.py
@@ -299,18 +299,18 @@ class RequisitionLine(models.Model):
def _get_valid_purchase_price(self, purchase_price):
price = 0
- taxes = ''
+ taxes = 24
human_last_update = purchase_price.human_last_update or datetime.min
system_last_update = purchase_price.system_last_update or datetime.min
- if purchase_price.taxes_product_id.type_tax_use == 'purchase':
- price = purchase_price.product_price
- taxes = purchase_price.taxes_product_id.id
+ #if purchase_price.taxes_product_id.type_tax_use == 'purchase':
+ price = purchase_price.product_price
+ taxes = purchase_price.taxes_product_id.id or 24
if system_last_update > human_last_update:
- if purchase_price.taxes_system_id.type_tax_use == 'purchase':
- price = purchase_price.system_price
- taxes = purchase_price.taxes_system_id.id
+ #if purchase_price.taxes_system_id.type_tax_use == 'purchase':
+ price = purchase_price.system_price
+ taxes = purchase_price.taxes_system_id.id or 24
return price, taxes
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index 4c48684d..bdf8f1eb 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -4,11 +4,13 @@ from odoo import fields, models, api, _
from odoo.exceptions import UserError, ValidationError
from datetime import datetime, timedelta
import logging, random, string, requests, math, json, re, qrcode, base64
+import pytz
from io import BytesIO
from collections import defaultdict
_logger = logging.getLogger(__name__)
+
class CancelReasonOrder(models.TransientModel):
_name = 'cancel.reason.order'
_description = 'Wizard for Cancel Reason order'
@@ -44,7 +46,7 @@ class CancelReasonOrder(models.TransientModel):
raise UserError('Attachment bukti wajib disertakan')
order.write({'attachment_bukti': self.attachment_bukti})
order.message_post(body='Attachment Bukti Cancel',
- attachment_ids=[self.attachment_bukti.id])
+ attachment_ids=[self.attachment_bukti.id])
if self.reason_cancel == 'ganti_quotation':
if self.nomor_so_pengganti:
order.write({'nomor_so_pengganti': self.nomor_so_pengganti})
@@ -53,7 +55,8 @@ class CancelReasonOrder(models.TransientModel):
order.confirm_cancel_order()
return {'type': 'ir.actions.act_window_close'}
-
+
+
class ShippingOption(models.Model):
_name = "shipping.option"
_description = "Shipping Option"
@@ -64,6 +67,7 @@ class ShippingOption(models.Model):
etd = fields.Char(string="Estimated Delivery Time")
sale_order_id = fields.Many2one('sale.order', string="Sale Order", ondelete="cascade")
+
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
@@ -73,7 +77,7 @@ class SaleOrderLine(models.Model):
if line.order_id:
now = fields.Datetime.now()
- initial_reason="Product Rejected"
+ initial_reason = "Product Rejected"
# Buat lognote untuk product yang di delete
log_note = (f"<li>Product '{line.product_id.name}' rejected. </li>"
@@ -113,10 +117,12 @@ class SaleOrderLine(models.Model):
return result
+
class SaleOrder(models.Model):
_inherit = "sale.order"
- ongkir_ke_xpdc = fields.Float(string='Ongkir ke Ekspedisi', help='Biaya ongkir ekspedisi', copy=False, index=True, tracking=3)
+ ongkir_ke_xpdc = fields.Float(string='Ongkir ke Ekspedisi', help='Biaya ongkir ekspedisi', copy=False, index=True,
+ tracking=3)
metode_kirim_ke_xpdc = fields.Selection([
('indoteknik_deliv', 'Indoteknik Delivery'),
@@ -127,21 +133,31 @@ class SaleOrder(models.Model):
('other', 'Other'),
], string='Metode Kirim Ke Ekspedisi', copy=False, index=True, tracking=3)
+ notes = fields.Text(string="Notes", tracking=3)
koli_lines = fields.One2many('sales.order.koli', 'sale_order_id', string='Sales Order Koli', auto_join=True)
fulfillment_line_v2 = fields.One2many('sales.order.fulfillment.v2', 'sale_order_id', string='Fullfillment2')
fullfillment_line = fields.One2many('sales.order.fullfillment', 'sales_order_id', string='Fullfillment')
reject_line = fields.One2many('sales.order.reject', 'sale_order_id', string='Reject Lines')
- order_sales_match_line = fields.One2many('sales.order.purchase.match', 'sales_order_id', string='Purchase Match Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True)
- total_margin = fields.Float('Total Margin', compute='_compute_total_margin', help="Total Margin in Sales Order Header")
- total_percent_margin = fields.Float('Total Percent Margin', compute='_compute_total_percent_margin', help="Total % Margin in Sales Order Header")
- total_margin_excl_third_party = fields.Float('Before Margin', help="Before Margin in Sales Order Header")
+ order_sales_match_line = fields.One2many('sales.order.purchase.match', 'sales_order_id',
+ string='Purchase Match Lines',
+ states={'cancel': [('readonly', True)], 'done': [('readonly', True)]},
+ copy=True)
+ total_margin = fields.Float('Total Margin', compute='_compute_total_margin',
+ help="Total Margin in Sales Order Header")
+ total_before_margin = fields.Float('Total Before Margin', compute='_compute_total_before_margin',
+ help="Total Margin in Sales Order Header")
+ total_percent_margin = fields.Float('Total Percent Margin', compute='_compute_total_percent_margin',
+ help="Total % Margin in Sales Order Header")
+ total_margin_excl_third_party = fields.Float('Before Margin', help="Before Margin in Sales Order Header",
+ compute='_compute_total_margin_excl_third_party')
approval_status = fields.Selection([
('pengajuan1', 'Approval Manager'),
('pengajuan2', 'Approval Pimpinan'),
('approved', 'Approved'),
], string='Approval Status', readonly=True, copy=False, index=True, tracking=3)
carrier_id = fields.Many2one('delivery.carrier', string='Shipping Method', tracking=3)
- have_visit_service = fields.Boolean(string='Have Visit Service', compute='_have_visit_service', help='To compute is customer get visit service')
+ have_visit_service = fields.Boolean(string='Have Visit Service', compute='_have_visit_service',
+ help='To compute is customer get visit service')
delivery_amt = fields.Float(string='Delivery Amt', copy=False)
shipping_cost_covered = fields.Selection([
('indoteknik', 'Indoteknik'),
@@ -151,7 +167,8 @@ class SaleOrder(models.Model):
('indoteknik', 'Indoteknik'),
('customer', 'Customer')
], string='Shipping Paid by', help='Siapa yang talangin dulu Biaya ekspedisi-nya?', copy=False, tracking=3)
- sales_tax_id = fields.Many2one('account.tax', string='Tax', domain=['|', ('active', '=', False), ('active', '=', True)])
+ sales_tax_id = fields.Many2one('account.tax', string='Tax',
+ domain=['|', ('active', '=', False), ('active', '=', True)])
have_outstanding_invoice = fields.Boolean('Have Outstanding Invoice', compute='_have_outstanding_invoice')
have_outstanding_picking = fields.Boolean('Have Outstanding Picking', compute='_have_outstanding_picking')
have_outstanding_po = fields.Boolean('Have Outstanding PO', compute='_have_outstanding_po')
@@ -172,9 +189,14 @@ class SaleOrder(models.Model):
('sebagian', 'Sebagian Diproses'),
('menunggu', 'Menunggu Diproses'),
], copy=False)
- partner_purchase_order_name = fields.Char(string='Nama PO Customer', copy=False, help="Nama purchase order customer, diisi oleh customer melalui website.", tracking=3)
- partner_purchase_order_description = fields.Text(string='Keterangan PO Customer', copy=False, help="Keterangan purchase order customer, diisi oleh customer melalui website.", tracking=3)
- partner_purchase_order_file = fields.Binary(string='File PO Customer', copy=False, help="File purchase order customer, diisi oleh customer melalui website.")
+ partner_purchase_order_name = fields.Char(string='Nama PO Customer', copy=False,
+ help="Nama purchase order customer, diisi oleh customer melalui website.",
+ tracking=3)
+ partner_purchase_order_description = fields.Text(string='Keterangan PO Customer', copy=False,
+ help="Keterangan purchase order customer, diisi oleh customer melalui website.",
+ tracking=3)
+ partner_purchase_order_file = fields.Binary(string='File PO Customer', copy=False,
+ help="File purchase order customer, diisi oleh customer melalui website.")
payment_status = fields.Selection([
('pending', 'Pending'),
('capture', 'Capture'),
@@ -188,23 +210,28 @@ class SaleOrder(models.Model):
('partial_refund', 'Partial Refund'),
('partial_chargeback', 'Partial Chargeback'),
('authorize', 'Authorize'),
- ], tracking=True, string='Payment Status', help='Payment Gateway Status / Midtrans / Web, https://docs.midtrans.com/en/after-payment/status-cycle')
- date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ yang terakhir, tidak berpengaruh ke Accounting")
+ ], tracking=True, string='Payment Status',
+ help='Payment Gateway Status / Midtrans / Web, https://docs.midtrans.com/en/after-payment/status-cycle')
+ date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ',
+ help="Tanggal Kirim di cetakan SJ yang terakhir, tidak berpengaruh ke Accounting")
payment_type = fields.Char(string='Payment Type', help='Jenis pembayaran dengan Midtrans')
gross_amount = fields.Float(string='Gross Amount', help='Jumlah pembayaran yang dilakukan dengan Midtrans')
notification = fields.Char(string='Notification', help='Dapat membantu error dari approval')
delivery_service_type = fields.Char(string='Delivery Service Type', help='data dari rajaongkir')
- grand_total = fields.Monetary(string='Grand Total', help='Amount total + amount delivery', compute='_compute_grand_total')
- payment_link_midtrans = fields.Char(string='Payment Link', help='Url payment yg digenerate oleh midtrans, harap diserahkan ke customer agar dapat dilakukan pembayaran secara mandiri')
+ grand_total = fields.Monetary(string='Grand Total', help='Amount total + amount delivery',
+ compute='_compute_grand_total')
+ payment_link_midtrans = fields.Char(string='Payment Link',
+ help='Url payment yg digenerate oleh midtrans, harap diserahkan ke customer agar dapat dilakukan pembayaran secara mandiri')
payment_qr_code = fields.Binary("Payment QR Code")
- due_id = fields.Many2one('due.extension', string="Due Extension", readonly=True, tracking=True)
- vendor_approval_id = fields.Many2many('vendor.approval', string="Vendor Approval", readonly=True, tracking=True, copy=False)
+ due_id = fields.Many2one('due.extension', string="Due Extension", readonly=True, tracking=True)
+ vendor_approval_id = fields.Many2many('vendor.approval', string="Vendor Approval", readonly=True, tracking=True,
+ copy=False)
customer_type = fields.Selection([
('pkp', 'PKP'),
('nonpkp', 'Non PKP')
- ], required=True)
- sppkp = fields.Char(string="SPPKP", required=True, tracking=True)
- npwp = fields.Char(string="NPWP", required=True, tracking=True)
+ ], required=True, compute='_compute_partner_field')
+ sppkp = fields.Char(string="SPPKP", required=True, tracking=True, compute='_compute_partner_field')
+ npwp = fields.Char(string="NPWP", required=True, tracking=True, compute='_compute_partner_field')
purchase_total = fields.Monetary(string='Purchase Total', compute='_compute_purchase_total')
voucher_id = fields.Many2one(comodel_name='voucher', string='Voucher', copy=False)
applied_voucher_id = fields.Many2one(comodel_name='voucher', string='Applied Voucher', copy=False)
@@ -238,8 +265,10 @@ class SaleOrder(models.Model):
use_button = fields.Boolean(string='Using Calculate Selling Price', copy=False)
unreserve_id = fields.Many2one('stock.picking', 'Unreserve Picking')
voucher_shipping_id = fields.Many2one(comodel_name='voucher', string='Voucher Shipping', copy=False)
- margin_after_delivery_purchase = fields.Float(string='Margin After Delivery Purchase', compute='_compute_margin_after_delivery_purchase')
- percent_margin_after_delivery_purchase = fields.Float(string='% Margin After Delivery Purchase', compute='_compute_margin_after_delivery_purchase')
+ margin_after_delivery_purchase = fields.Float(string='Margin After Delivery Purchase',
+ compute='_compute_margin_after_delivery_purchase')
+ percent_margin_after_delivery_purchase = fields.Float(string='% Margin After Delivery Purchase',
+ compute='_compute_margin_after_delivery_purchase')
purchase_delivery_amt = fields.Float(string='Purchase Delivery Amount', compute='_compute_purchase_delivery_amount')
type_promotion = fields.Char(string='Type Promotion', compute='_compute_type_promotion')
partner_invoice_id = fields.Many2one(
@@ -253,11 +282,11 @@ class SaleOrder(models.Model):
'res.partner', string='Delivery Address', readonly=True, required=True,
states={'draft': [('readonly', False)], 'sent': [('readonly', False)], 'sale': [('readonly', False)]},
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", tracking=True)
-
+
payment_term_id = fields.Many2one(
'account.payment.term', string='Payment Terms', check_company=True, # Unrequired company
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", tracking=True)
-
+
total_weight = fields.Float(string='Total Weight', compute='_compute_total_weight')
pareto_status = fields.Selection([
('PR', 'Pareto Repeating'),
@@ -274,7 +303,7 @@ class SaleOrder(models.Model):
copy=False
)
shipping_method_picking = fields.Char(string='Shipping Method Picking', compute='_compute_shipping_method_picking')
-
+
reason_cancel = fields.Selection([
('harga_terlalu_mahal', 'Harga barang terlalu mahal'),
('harga_web_tidak_valid', 'Harga web tidak valid'),
@@ -295,7 +324,70 @@ class SaleOrder(models.Model):
string="Attachment Bukti Cancel", readonly=False,
)
nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3)
- shipping_option_id = fields.Many2one("shipping.option", string="Selected Shipping Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]")
+ shipping_option_id = fields.Many2one("shipping.option", string="Selected Shipping Option",
+ domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]")
+ hold_outgoing = fields.Boolean('Hold Outgoing SO', tracking=3)
+ state_ask_cancel = fields.Selection([
+ ('hold', 'Hold'),
+ ('approve', 'Approve')
+ ], tracking=True, string='State Cancel', copy=False)
+ date_hold = fields.Datetime(string='Date Hold', tracking=True, readonly=True, help='Waktu ketika SO di Hold'
+ )
+ date_unhold = fields.Datetime(string='Date Unhold', tracking=True, readonly=True, help='Waktu ketika SO di Unhold'
+ )
+
+ def _compute_total_margin_excl_third_party(self):
+ for order in self:
+ if order.amount_untaxed == 0:
+ order.total_margin_excl_third_party = 0
+ continue
+
+ # order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-delivery_amt-order.fee_third_party)) * 100, 2)
+ order.total_margin_excl_third_party = round((order.total_before_margin / (order.amount_untaxed)) * 100, 2)
+ # order.total_percent_margin = round((order.total_margin / (order.amount_untaxed)) * 100, 2)
+
+ def ask_retur_cancel_purchasing(self):
+ for rec in self:
+ if self.env.user.has_group('indoteknik_custom.group_role_purchasing'):
+ rec.state_ask_cancel = 'approve'
+ return {
+ 'type': 'ir.actions.client',
+ 'tag': 'display_notification',
+ 'params': {
+ 'title': 'Persetujuan Diberikan',
+ 'message': 'Proses cancel sudah disetujui',
+ 'type': 'success',
+ 'sticky': True
+ }
+ }
+ else:
+ rec.state_ask_cancel = 'hold'
+ return {
+ 'type': 'ir.actions.client',
+ 'tag': 'display_notification',
+ 'params': {
+ 'title': 'Menunggu Persetujuan',
+ 'message': 'Tim Purchasing akan memproses permintaan Anda',
+ 'type': 'warning',
+ 'sticky': False
+ }
+ }
+
+ def hold_unhold_qty_outgoing_so(self):
+ if self.hold_outgoing == True:
+ self.hold_outgoing = False
+ self.date_unhold = fields.Datetime.now()
+ else:
+ pick = self.env['stock.picking'].search([
+ ('sale_id', '=', self.id),
+ ('state', 'not in', ['cancel', 'done']),
+ ('name', 'ilike', 'BU/PICK/%')
+ ])
+ for picking in pick:
+ picking.do_unreserve()
+ self.hold_outgoing = True
+ self.date_hold = fields.Datetime.now()
+
def _validate_uniform_taxes(self):
for order in self:
@@ -306,18 +398,18 @@ class SaleOrder(models.Model):
tax_sets.add(tax_ids)
if len(tax_sets) > 1:
raise ValidationError("Semua produk dalam Sales Order harus memiliki kombinasi pajak yang sama.")
-
- @api.constrains('fee_third_party', 'delivery_amt', 'biaya_lain_lain')
- def _check_total_margin_excl_third_party(self):
- for rec in self:
- if rec.fee_third_party == 0 and rec.total_margin_excl_third_party != rec.total_percent_margin:
- # Gunakan direct SQL atau flag context untuk menghindari rekursi
- self.env.cr.execute("""
- UPDATE sale_order
- SET total_margin_excl_third_party = %s
- WHERE id = %s
- """, (rec.total_percent_margin, rec.id))
- self.invalidate_cache()
+
+ # @api.constrains('fee_third_party', 'delivery_amt', 'biaya_lain_lain')
+ # def _check_total_margin_excl_third_party(self):
+ # for rec in self:
+ # if rec.fee_third_party == 0 and rec.total_margin_excl_third_party != rec.total_percent_margin:
+ # # Gunakan direct SQL atau flag context untuk menghindari rekursi
+ # self.env.cr.execute("""
+ # UPDATE sale_order
+ # SET total_margin_excl_third_party = %s
+ # WHERE id = %s
+ # """, (rec.total_percent_margin, rec.id))
+ # self.invalidate_cache()
@api.constrains('shipping_option_id')
def _check_shipping_option(self):
@@ -354,14 +446,14 @@ class SaleOrder(models.Model):
def action_indoteknik_estimate_shipping(self):
if not self.real_shipping_id.kota_id.is_jabodetabek:
raise UserError('Estimasi ongkir hanya bisa dilakukan di kota Jabodetabek')
-
+
total_weight = 0
missing_weight_products = []
for line in self.order_line:
if line.weight > 0:
total_weight += line.weight * line.product_uom_qty
- line.product_id.weight = line.weight
+ line.product_id.weight = line.weight
else:
missing_weight_products.append(line.product_id.name)
@@ -371,10 +463,10 @@ class SaleOrder(models.Model):
if total_weight == 0:
raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.")
-
+
if total_weight < 10:
total_weight = 10
-
+
self.delivery_amt = total_weight * 3000
shipping_option = self.env["shipping.option"].create({
@@ -407,7 +499,7 @@ class SaleOrder(models.Model):
for line in self.order_line:
if line.weight > 0:
total_weight += line.weight * line.product_uom_qty
- line.product_id.weight = line.weight
+ line.product_id.weight = line.weight
else:
missing_weight_products.append(line.product_id.name)
@@ -432,11 +524,11 @@ class SaleOrder(models.Model):
etd = cost_detail['cost'][0]['etd']
value = cost_detail['cost'][0]['value']
shipping_options.append((service, description, etd, value, courier['code']))
-
+
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,
@@ -446,22 +538,20 @@ 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]} hari, 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.")
-
def _call_rajaongkir_api(self, total_weight, destination_subsdistrict_id):
url = 'https://pro.rajaongkir.com/api/cost'
@@ -483,7 +573,7 @@ class SaleOrder(models.Model):
if response.status_code == 200:
return response.json()
return None
-
+
def _normalize_city_name(self, city_name):
city_name = city_name.lower()
@@ -503,7 +593,7 @@ class SaleOrder(models.Model):
}
normalized_city_name = self._normalize_city_name(city_name)
-
+
response = requests.get(url, headers=headers)
if response.status_code == 200:
city_data = response.json()
@@ -517,7 +607,7 @@ class SaleOrder(models.Model):
headers = {
'key': '9b1310f644056d84d60b0af6bb21611a',
}
-
+
response = requests.get(url, headers=headers)
if response.status_code == 200:
subdistrict_data = response.json()
@@ -529,15 +619,15 @@ class SaleOrder(models.Model):
return subdistrict['subdistrict_id']
return None
-
def _compute_type_promotion(self):
for rec in self:
promotion_types = []
for promotion in rec.order_promotion_ids:
for line_program in promotion.program_line_id:
if line_program.promotion_type:
- promotion_types.append(dict(line_program._fields['promotion_type'].selection).get(line_program.promotion_type))
-
+ promotion_types.append(
+ dict(line_program._fields['promotion_type'].selection).get(line_program.promotion_type))
+
rec.type_promotion = ', '.join(sorted(set(promotion_types)))
def _compute_purchase_delivery_amount(self):
@@ -561,11 +651,14 @@ class SaleOrder(models.Model):
delivery_amt = order.delivery_amt
else:
delivery_amt = 0
- order.percent_margin_after_delivery_purchase = round((order.margin_after_delivery_purchase / (order.amount_untaxed-delivery_amt-order.fee_third_party-order.biaya_lain_lain)) * 100, 2)
+ order.percent_margin_after_delivery_purchase = round((order.margin_after_delivery_purchase / (
+ order.amount_untaxed - delivery_amt - order.fee_third_party - order.biaya_lain_lain)) * 100, 2)
def _compute_date_kirim(self):
for rec in self:
- picking = self.env['stock.picking'].search([('sale_id', '=', rec.id), ('state', 'not in', ['cancel']), ('name', 'not ilike', 'BU/PICK/%')], order='date_doc_kirim desc', limit=1)
+ picking = self.env['stock.picking'].search(
+ [('sale_id', '=', rec.id), ('state', 'not in', ['cancel']), ('name', 'not ilike', 'BU/PICK/%')],
+ order='date_doc_kirim desc', limit=1)
rec.date_kirim_ril = picking.date_doc_kirim
rec.date_status_done = picking.date_done
rec.date_driver_arrival = picking.driver_arrival_date
@@ -577,54 +670,55 @@ class SaleOrder(models.Model):
'so_ids': [x.id for x in self]
}
return action
-
+
def _compute_fullfillment(self):
for rec in self:
- # rec.fullfillment_line.unlink()
- #
- # for line in rec.order_line:
- # line._compute_reserved_from()
+ # rec.fullfillment_line.unlink()
+ #
+ # for line in rec.order_line:
+ # line._compute_reserved_from()
rec.compute_fullfillment = True
@api.depends('date_order', 'estimated_arrival_days', 'state', 'estimated_arrival_days_start')
def _compute_eta_date(self):
- current_date = datetime.now().date()
- for rec in self:
- if rec.date_order and rec.state not in ['cancel'] and rec.estimated_arrival_days and rec.estimated_arrival_days_start:
+ current_date = datetime.now().date()
+ for rec in self:
+ if rec.date_order and rec.state not in [
+ 'cancel'] and rec.estimated_arrival_days and rec.estimated_arrival_days_start:
rec.eta_date = current_date + timedelta(days=rec.estimated_arrival_days)
rec.eta_date_start = current_date + timedelta(days=rec.estimated_arrival_days_start)
else:
rec.eta_date = False
rec.eta_date_start = False
-
-
- def get_days_until_next_business_day(self,start_date=None, *args, **kwargs):
+
+ def get_days_until_next_business_day(self, start_date=None, *args, **kwargs):
today = start_date or datetime.today().date()
offset = 0 # Counter jumlah hari yang ditambahkan
holiday = self.env['hr.public.holiday']
- while True :
+ while True:
today += timedelta(days=1)
offset += 1
-
+
if today.weekday() >= 5:
continue
is_holiday = holiday.search([("start_date", "=", today)])
if is_holiday:
continue
-
+
break
return offset
-
+
def calculate_sla_by_vendor(self, products):
product_ids = products.mapped('product_id.id') # Kumpulkan semua ID produk
include_instant = True # Default True, tetapi bisa menjadi False
# Cek apakah SEMUA produk memiliki qty_free_bandengan >= qty_needed
- all_fast_products = all(product.product_id.qty_free_bandengan >= product.product_uom_qty for product in products)
+ all_fast_products = all(
+ product.product_id.qty_free_bandengan >= product.product_uom_qty for product in products)
if all_fast_products:
return {'slatime': 1, 'include_instant': include_instant}
@@ -634,7 +728,7 @@ class SaleOrder(models.Model):
('is_winner', '=', True)
])
- max_slatime = 1
+ max_slatime = 1
for vendor in vendors:
vendor_sla = self.env['vendor.sla'].search([('id_vendor', '=', vendor.vendor_id.id)], limit=1)
@@ -643,26 +737,26 @@ class SaleOrder(models.Model):
if vendor_sla.unit == 'hari':
vendor_duration = vendor_sla.duration * 24 * 60
include_instant = False
- else :
+ else:
vendor_duration = vendor_sla.duration * 60
include_instant = True
-
+
estimation_sla = (1 * 24 * 60) + vendor_duration
estimation_sla_days = estimation_sla / (24 * 60)
slatime = math.ceil(estimation_sla_days)
-
+
max_slatime = max(max_slatime, slatime)
return {'slatime': max_slatime, 'include_instant': include_instant}
-
+
def _calculate_etrts_date(self):
for rec in self:
if not rec.date_order:
rec.expected_ready_to_ship = False
return
-
+
current_date = datetime.now().date()
-
+
max_slatime = 1 # Default SLA jika tidak ada
slatime = self.calculate_sla_by_vendor(rec.order_line)
max_slatime = max(max_slatime, slatime['slatime'])
@@ -670,29 +764,28 @@ class SaleOrder(models.Model):
sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1
if not rec.estimated_arrival_days:
rec.estimated_arrival_days = sum_days
-
+
eta_date = current_date + timedelta(days=sum_days)
rec.commitment_date = eta_date
rec.expected_ready_to_ship = eta_date
-
- @api.depends("order_line.product_id", "date_order")
- def _compute_etrts_date(self): #Function to calculate Estimated Ready To Ship Date
+
+ @api.depends("order_line.product_id", "date_order")
+ def _compute_etrts_date(self): # Function to calculate Estimated Ready To Ship Date
self._calculate_etrts_date()
-
-
+
def _validate_expected_ready_ship_date(self):
for rec in self:
if rec.expected_ready_to_ship and rec.commitment_date:
current_date = datetime.now().date()
# Hanya membandingkan tanggal saja, tanpa jam
expected_date = rec.expected_ready_to_ship.date()
-
+
max_slatime = 1 # Default SLA jika tidak ada
slatime = self.calculate_sla_by_vendor(rec.order_line)
max_slatime = max(max_slatime, slatime['slatime'])
sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1
eta_minimum = current_date + timedelta(days=sum_days)
-
+
if expected_date < eta_minimum:
rec.expected_ready_to_ship = eta_minimum
raise ValidationError(
@@ -701,16 +794,16 @@ class SaleOrder(models.Model):
)
else:
rec.commitment_date = rec.expected_ready_to_ship
-
-
- @api.onchange('expected_ready_to_ship') #Hangle Onchange form Expected Ready to Ship
+
+ @api.onchange('expected_ready_to_ship') # Hangle Onchange form Expected Ready to Ship
def _onchange_expected_ready_ship_date(self):
self._validate_expected_ready_ship_date()
def _set_etrts_date(self):
for order in self:
if order.state in ('done', 'cancel', 'sale'):
- raise UserError(_("You cannot change the Estimated Ready To Ship Date on a done, sale or cancelled order."))
+ raise UserError(
+ _("You cannot change the Estimated Ready To Ship Date on a done, sale or cancelled order."))
# order.move_lines.write({'estimated_ready_ship_date': order.estimated_ready_ship_date})
def _prepare_invoice(self):
@@ -722,10 +815,12 @@ class SaleOrder(models.Model):
self.ensure_one()
journal = self.env['account.move'].with_context(default_move_type='out_invoice')._get_default_journal()
if not journal:
- raise UserError(_('Please define an accounting sales journal for the company %s (%s).') % (self.company_id.name, self.company_id.id))
+ raise UserError(
+ _('Please define an accounting sales journal for the company %s (%s).') % (self.company_id.name,
+ self.company_id.id))
parent_id = self.partner_id.parent_id
- parent_id = parent_id if parent_id else self.partner_id
+ parent_id = parent_id if parent_id else self.partner_id
invoice_vals = {
'ref': self.client_order_ref or '',
@@ -742,7 +837,8 @@ class SaleOrder(models.Model):
'partner_id': parent_id.id,
'partner_shipping_id': parent_id.id,
'real_invoice_id': self.real_invoice_id.id,
- 'fiscal_position_id': (self.fiscal_position_id or self.fiscal_position_id.get_fiscal_position(self.partner_invoice_id.id)).id,
+ 'fiscal_position_id': (self.fiscal_position_id or self.fiscal_position_id.get_fiscal_position(
+ self.partner_invoice_id.id)).id,
'partner_bank_id': self.company_id.partner_id.bank_ids[:1].id,
'journal_id': journal.id, # company comes from the journal
'invoice_origin': self.name,
@@ -758,10 +854,10 @@ class SaleOrder(models.Model):
def _validate_email(self):
rule_regex = self.env['ir.config_parameter'].sudo().get_param('sale.order.validate_email') or ''
pattern = rf'^{rule_regex}$'
-
+
if self.email and not re.match(pattern, self.email):
raise UserError('Email yang anda input kurang valid')
-
+
# @api.constrains('delivery_amt', 'carrier_id', 'shipping_cost_covered')
def _validate_delivery_amt(self):
is_indoteknik = self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik'
@@ -773,13 +869,14 @@ class SaleOrder(models.Model):
raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum diisi.')
else:
raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum diisi.')
-
+
if self.delivery_amt < 100:
if self.carrier_id.id == 1:
- raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi tarif minimum.')
+ raise UserError(
+ 'Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi tarif minimum.')
else:
- raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi tarif minimum.')
-
+ raise UserError(
+ 'Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi tarif minimum.')
# if self.delivery_amt < 5000:
# if (self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []):
@@ -788,7 +885,6 @@ class SaleOrder(models.Model):
# else:
# raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi jumlah minimum.')
-
def override_allow_create_invoice(self):
if not self.env.user.is_accounting:
raise UserError('Hanya Finance Accounting yang dapat klik tombol ini')
@@ -829,14 +925,14 @@ class SaleOrder(models.Model):
'sale_ids': [x.id for x in self]
}
return action
-
+
def open_form_multi_update_state(self):
action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_quotation_so_multi_update')
action['context'] = {
'quotation_ids': [x.id for x in self]
}
return action
-
+
def action_multi_update_invoice_status(self):
for sale in self:
sale.update({
@@ -849,7 +945,7 @@ class SaleOrder(models.Model):
for line in order.order_line:
total += line.vendor_subtotal
order.purchase_total = total
-
+
def check_data_real_delivery_address(self):
real_delivery_address = self.real_shipping_id
@@ -869,8 +965,8 @@ class SaleOrder(models.Model):
def generate_payment_link_midtrans_sales_order(self):
# midtrans_url = 'https://app.sandbox.midtrans.com/snap/v1/transactions' # dev - sandbox
# midtrans_auth = 'Basic U0ItTWlkLXNlcnZlci1uLVY3ZDJjMlpCMFNWRUQyOU95Q1dWWXA6' # dev - sandbox
- midtrans_url = 'https://app.midtrans.com/snap/v1/transactions' # production
- midtrans_auth = 'Basic TWlkLXNlcnZlci1SbGMxZ2gzWGpSVW5scl9JblZzTV9OTnU6' # production
+ midtrans_url = 'https://app.midtrans.com/snap/v1/transactions' # production
+ midtrans_auth = 'Basic TWlkLXNlcnZlci1SbGMxZ2gzWGpSVW5scl9JblZzTV9OTnU6' # production
so_number = self.name
so_number = so_number.replace('/', '-')
so_grandtotal = math.floor(self.grand_total)
@@ -885,7 +981,8 @@ class SaleOrder(models.Model):
if check_response.status_code == 200:
status_response = check_response.json()
- if status_response.get('transaction_status') == 'expire' or status_response.get('transaction_status') == 'cancel':
+ if status_response.get('transaction_status') == 'expire' or status_response.get(
+ 'transaction_status') == 'cancel':
so_number = so_number + '-cpl'
json_data = {
@@ -933,8 +1030,7 @@ class SaleOrder(models.Model):
if line.product_id.type == 'product':
line_no += 1
line.line_no = line_no
-
-
+
def write(self, vals):
if 'carrier_id' in vals:
for picking in self.picking_ids:
@@ -947,7 +1043,7 @@ class SaleOrder(models.Model):
('state', 'in', so_state),
('so_status', '!=', 'terproses'),
])
-
+
for sale in sales:
picking_states = ['draft', 'assigned', 'confirmed', 'waiting']
have_outstanding_pick = any(x.state in picking_states for x in sale.picking_ids)
@@ -961,16 +1057,16 @@ class SaleOrder(models.Model):
sale.so_status = 'terproses'
else:
sale.so_status = 'menunggu'
-
+
for picking in sale.picking_ids:
sum_qty_pick = sum(move_line.product_uom_qty for move_line in picking.move_ids_without_package)
sum_qty_reserved = sum(move_line.product_uom_qty for move_line in picking.move_line_ids_without_package)
if picking.state == 'done':
continue
- elif sum_qty_pick == sum_qty_reserved and not picking.date_reserved:# baru ke reserved
+ elif sum_qty_pick == sum_qty_reserved and not picking.date_reserved: # baru ke reserved
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
picking.date_reserved = current_time
- elif sum_qty_pick == sum_qty_reserved:# sudah ada data reserved
+ elif sum_qty_pick == sum_qty_reserved: # sudah ada data reserved
picking.date_reserved = picking.date_reserved
else:
picking.date_reserved = ''
@@ -994,11 +1090,18 @@ class SaleOrder(models.Model):
# return [('id', 'not in', order_ids)]
# return ['&', ('order_line.invoice_lines.move_id.move_type', 'in', ('out_invoice', 'out_refund')), ('order_line.invoice_lines.move_id', operator, value)]
+ @api.depends('partner_id')
+ def _compute_partner_field(self):
+ for order in self:
+ partner = order.partner_id.parent_id or order.partner_id
+ order.npwp = partner.npwp
+ order.sppkp = partner.sppkp
+ order.customer_type = partner.customer_type
@api.onchange('partner_id')
def onchange_partner_contact(self):
parent_id = self.partner_id.parent_id
- parent_id = parent_id if parent_id else self.partner_id
+ parent_id = parent_id if parent_id else self.partner_id
self.npwp = parent_id.npwp
self.sppkp = parent_id.sppkp
@@ -1006,13 +1109,13 @@ class SaleOrder(models.Model):
self.email = parent_id.email
self.pareto_status = parent_id.pareto_status
self.user_id = parent_id.user_id
-
+
@api.onchange('partner_id')
def onchange_partner_id(self):
# INHERIT
result = super(SaleOrder, self).onchange_partner_id()
parent_id = self.partner_id.parent_id
- parent_id = parent_id if parent_id else self.partner_id
+ parent_id = parent_id if parent_id else self.partner_id
self.partner_invoice_id = parent_id
return result
@@ -1045,16 +1148,16 @@ class SaleOrder(models.Model):
minimum_amount = 20000000
for order in self:
order.have_visit_service = self.amount_total > minimum_amount
-
+
def _get_helper_ids(self):
helper_ids_str = self.env['ir.config_parameter'].sudo().get_param('sale.order.user_helper_ids')
return helper_ids_str.split(', ')
-
+
def write(self, values):
helper_ids = self._get_helper_ids()
if str(self.env.user.id) in helper_ids:
values['helper_by_id'] = self.env.user.id
-
+
return super(SaleOrder, self).write(values)
def check_due(self):
@@ -1072,21 +1175,21 @@ class SaleOrder(models.Model):
def _validate_order(self):
if self.payment_term_id.id == 31 and self.total_percent_margin < 25:
raise UserError("Jika ingin menggunakan Tempo 90 Hari maka margin harus di atas 25%")
-
- if self.warehouse_id.id != 8 and self.warehouse_id.id != 10: #GD Bandengan
+
+ if self.warehouse_id.id != 8 and self.warehouse_id.id != 10: # GD Bandengan
raise UserError('Gudang harus Bandengan')
-
+
if self.state not in ['draft', 'sent']:
raise UserError("Status harus draft atau sent")
-
+
self._validate_npwp()
-
+
def _validate_npwp(self):
num_digits = sum(c.isdigit() for c in self.npwp)
if num_digits < 10:
raise UserError("NPWP harus memiliki minimal 10 digit")
-
+
# pattern = r'^\d{10,}$'
# return re.match(pattern, self.npwp) is not None
@@ -1114,7 +1217,8 @@ class SaleOrder(models.Model):
if (partner.customer_type == 'pkp' or order.customer_type == 'pkp') and order.sppkp != partner.sppkp:
raise UserError("SPPKP berbeda pada Master Data Customer")
if not order.client_order_ref and order.create_date > datetime(2024, 6, 27):
- raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
+ raise UserError(
+ "Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
if not order.user_id.active:
raise UserError("Salesperson sudah tidak aktif, mohon diisi yang benar pada data SO dan Contact")
@@ -1122,20 +1226,23 @@ class SaleOrder(models.Model):
for order in self:
for line in order.order_line:
if 'bom-it' in line.name.lower() or 'bom' in line.product_id.default_code.lower() if line.product_id.default_code else False:
- search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id)],order='name desc')
+ search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id), ('sale_order', '=', order.id), ('state', '!=', 'cancel')],
+ order='name desc')
if search_bom:
- confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed')
+ 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.")
+ raise UserError(
+ "Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.")
else:
raise UserError("Product BOM tidak di temukan di manufacturing orders, silahkan hubungi MD")
-
+
def check_duplicate_product(self):
for order in self:
for line in order.order_line:
- search_product = self.env['sale.order.line'].search([('product_id', '=', line.product_id.id), ('order_id', '=', order.id)])
- if len(search_product) > 1:
- raise UserError("Terdapat DUPLIKASI data pada Product {}".format(line.product_id.display_name))
+ search_product = self.env['sale.order.line'].search(
+ [('product_id', '=', line.product_id.id), ('order_id', '=', order.id)])
+ if len(search_product) > 1:
+ raise UserError("Terdapat DUPLIKASI data pada Product {}".format(line.product_id.display_name))
def sale_order_approve(self):
self.check_duplicate_product()
@@ -1158,11 +1265,13 @@ class SaleOrder(models.Model):
SYSTEM_UID = 25
FROM_WEBSITE = order.create_uid.id == SYSTEM_UID
- if FROM_WEBSITE and main_parent.use_so_approval and order.web_approval not in ['cust_procurement','cust_director']:
+ if FROM_WEBSITE and main_parent.use_so_approval and order.web_approval not in ['cust_procurement',
+ 'cust_director']:
raise UserError("This order not yet approved by customer procurement or director")
if not order.client_order_ref and order.create_date > datetime(2024, 6, 27):
- raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
+ raise UserError(
+ "Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
if not order.commitment_date and order.create_date > datetime(2024, 9, 12):
raise UserError("Expected Delivery Date kosong, wajib diisi")
@@ -1170,8 +1279,10 @@ class SaleOrder(models.Model):
if not order.real_shipping_id:
UserError('Real Delivery Address harus di isi')
- if order.validate_partner_invoice_due():
- return self._create_notification_action('Notification','Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension')
+ if not self.env.context.get('due_approve', []):
+ if order.validate_partner_invoice_due():
+ return self._create_notification_action('Notification',
+ 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension')
term_days = 0
for term_line in order.payment_term_id.line_ids:
@@ -1189,13 +1300,15 @@ class SaleOrder(models.Model):
if (partner.customer_type == 'pkp' or order.customer_type == 'pkp') and order.sppkp != partner.sppkp:
raise UserError("SPPKP berbeda pada Master Data Customer")
if not order.client_order_ref and order.create_date > datetime(2024, 6, 27):
- raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
+ raise UserError(
+ "Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
if not order.user_id.active:
raise UserError("Salesperson sudah tidak aktif, mohon diisi yang benar pada data SO dan Contact")
-
- if order.validate_partner_invoice_due():
- return self._create_notification_action('Notification', 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension')
-
+
+ # if order.validate_partner_invoice_due():
+ # return self._create_notification_action('Notification',
+ # 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension')
+
if order._requires_approval_margin_leader():
order.approval_status = 'pengajuan2'
return self._create_approval_notification('Pimpinan')
@@ -1205,9 +1318,9 @@ class SaleOrder(models.Model):
self.check_limit_so_to_invoice()
order.approval_status = 'pengajuan1'
return self._create_approval_notification('Sales Manager')
-
+
raise UserError("Bisa langsung Confirm")
-
+
def send_notif_to_salesperson(self, cancel=False):
if not cancel:
@@ -1225,7 +1338,9 @@ class SaleOrder(models.Model):
salesperson_data = {}
for rec in grouping_so:
if rec.user_id.id not in salesperson_data:
- salesperson_data[rec.user_id.id] = {'name': rec.user_id.name, 'orders': [], 'total_amount': 0, 'sum_total_amount': 0, 'business_partner': '', 'site': ''} # Menetapkan nilai awal untuk 'site'
+ salesperson_data[rec.user_id.id] = {'name': rec.user_id.name, 'orders': [], 'total_amount': 0,
+ 'sum_total_amount': 0, 'business_partner': '',
+ 'site': ''} # Menetapkan nilai awal untuk 'site'
if rec.picking_ids:
if not any(picking.state in ['assigned', 'confirmed', 'waiting'] for picking in rec.picking_ids):
continue
@@ -1244,7 +1359,8 @@ class SaleOrder(models.Model):
})
salesperson_data[rec.user_id.id]['sum_total_amount'] += order_total_amount
salesperson_data[rec.user_id.id]['business_partner'] = grouping_so[0].partner_id.main_parent_id.name
- salesperson_data[rec.user_id.id]['site'] = grouping_so[0].partner_id.site_id.name # Menambahkan nilai hanya jika ada
+ salesperson_data[rec.user_id.id]['site'] = grouping_so[
+ 0].partner_id.site_id.name # Menambahkan nilai hanya jika ada
# Kirim email untuk setiap salesperson
for salesperson_id, data in salesperson_data.items():
@@ -1268,9 +1384,9 @@ class SaleOrder(models.Model):
template = self.env.ref('indoteknik_custom.mail_template_sale_order_notification_to_salesperson')
email_body = template.body_html.replace('${table_content}', table_content)
email_body = email_body.replace('${salesperson_name}', data['name'])
- email_body = email_body.replace('${sum_total_amount}', str(data['sum_total_amount']))
- email_body = email_body.replace('${site}', str(data['site']))
- email_body = email_body.replace('${business_partner}', str(data['business_partner']))
+ email_body = email_body.replace('${sum_total_amount}', str(data['sum_total_amount']))
+ email_body = email_body.replace('${site}', str(data['site']))
+ email_body = email_body.replace('${business_partner}', str(data['business_partner']))
# Kirim email
self.env['mail.mail'].create({
'subject': 'Notification: Sale Orders',
@@ -1306,8 +1422,9 @@ class SaleOrder(models.Model):
if (outstanding_amount + rec.amount_total) >= block_stage:
if block_stage != 0:
remaining_credit_limit = block_stage - outstanding_amount
- raise UserError(_("%s is in Blocking Stage, Remaining credit limit is %s, from %s and outstanding %s")
- % (rec.partner_id.name, remaining_credit_limit, block_stage, outstanding_amount))
+ raise UserError(
+ _("%s is in Blocking Stage, Remaining credit limit is %s, from %s and outstanding %s")
+ % (rec.partner_id.name, remaining_credit_limit, block_stage, outstanding_amount))
def check_limit_so_to_invoice(self):
for rec in self:
@@ -1329,7 +1446,8 @@ class SaleOrder(models.Model):
# Validasi limit
if remaining_credit_limit <= 0 and block_stage > 0 and not is_cbd:
- raise UserError(_("The credit limit for %s will exceed the Blocking Stage if the Sale Order is confirmed. The remaining credit limit is %s, from %s and the outstanding amount is %s.")
+ raise UserError(
+ _("The credit limit for %s will exceed the Blocking Stage if the Sale Order is confirmed. The remaining credit limit is %s, from %s and the outstanding amount is %s.")
% (rec.partner_id.name, block_stage - current_total, block_stage, outstanding_amount))
def validate_different_vendor(self):
@@ -1339,11 +1457,11 @@ class SaleOrder(models.Model):
if self.vendor_approval_id and all(v.state != 'draft' for v in self.vendor_approval_id):
return False
-
+
different_vendor = self.order_line.filtered(
lambda l: l.vendor_id and l.vendor_md_id and l.vendor_id.id != l.vendor_md_id.id
)
-
+
if different_vendor:
vendor_approvals = []
for line in different_vendor:
@@ -1369,15 +1487,14 @@ class SaleOrder(models.Model):
if line.purchase_price_md else False
),
})
-
+
vendor_approvals.append(vendor_approval.id)
-
+
self.vendor_approval_id = [(4, vid) for vid in vendor_approvals]
return True
else:
return False
-
def action_confirm(self):
for order in self:
order._validate_uniform_taxes()
@@ -1387,38 +1504,42 @@ class SaleOrder(models.Model):
order.check_limit_so_to_invoice()
if self.validate_different_vendor() and not self.vendor_approval:
return self._create_notification_action('Notification', 'Terdapat Vendor yang berbeda dengan MD Vendor')
-
+
order.check_data_real_delivery_address()
order.sale_order_check_approve()
order._validate_order()
order.order_line.validate_line()
-
+
main_parent = order.partner_id.get_main_parent()
SYSTEM_UID = 25
FROM_WEBSITE = order.create_uid.id == SYSTEM_UID
-
- if FROM_WEBSITE and main_parent.use_so_approval and order.web_approval not in ['cust_procurement', 'cust_director']:
+
+ if FROM_WEBSITE and main_parent.use_so_approval and order.web_approval not in ['cust_procurement',
+ 'cust_director']:
raise UserError("This order not yet approved by customer procurement or director")
if not order.client_order_ref and order.create_date > datetime(2024, 6, 27):
- raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
+ raise UserError(
+ "Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
if not order.commitment_date and order.create_date > datetime(2024, 9, 12):
raise UserError("Expected Delivery Date kosong, wajib diisi")
-
+
if not order.real_shipping_id:
UserError('Real Delivery Address harus di isi')
-
- if order.validate_partner_invoice_due():
- return self._create_notification_action('Notification', 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension')
-
+
+ if not self.env.context.get('due_approve', []):
+ if order.validate_partner_invoice_due():
+ return self._create_notification_action('Notification',
+ 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension')
+
if order._requires_approval_margin_leader():
order.approval_status = 'pengajuan2'
return self._create_approval_notification('Pimpinan')
elif order._requires_approval_margin_manager():
order.approval_status = 'pengajuan1'
return self._create_approval_notification('Sales Manager')
-
+
order.approval_status = 'approved'
order._set_sppkp_npwp_contact()
order.calculate_line_no()
@@ -1432,7 +1553,7 @@ class SaleOrder(models.Model):
for line in order.order_line:
if line.display_type == 'line_note':
note.append(line.name)
-
+
if order.picking_ids:
# Sort picking_ids by creation date to get the most recent one
latest_picking = order.picking_ids.sorted(key=lambda p: p.create_date, reverse=True)[0]
@@ -1441,10 +1562,12 @@ class SaleOrder(models.Model):
def action_cancel(self):
# TODO stephan prevent cancel if have invoice, do, and po
+ if self.state_ask_cancel != 'approve' and self.state not in ['draft', 'sent']:
+ raise UserError("Anda harus approval purchasing terlebih dahulu")
main_parent = self.partner_id.get_main_parent()
if self._name != 'sale.order':
return super(SaleOrder, self).action_cancel()
-
+
if self.have_outstanding_invoice:
raise UserError("Invoice harus di Cancel dahulu")
@@ -1455,7 +1578,7 @@ class SaleOrder(models.Model):
for line in self.order_line:
if line.qty_delivered > 0:
raise UserError("DO yang done harus di-Return oleh Logistik")
-
+
if not self.web_approval:
self.web_approval = 'company'
# elif self.have_outstanding_po:
@@ -1485,11 +1608,11 @@ class SaleOrder(models.Model):
def validate_partner_invoice_due(self):
parent_id = self.partner_id.parent_id.id
- parent_id = parent_id if parent_id else self.partner_id.id
+ parent_id = parent_id if parent_id else self.partner_id.id
if self.due_id and self.due_id.is_approve == False:
raise UserError('Document Over Due Yang Anda Buat Belum Di Approve')
-
+
query = [
('partner_id', '=', parent_id),
('state', '=', 'posted'),
@@ -1499,40 +1622,39 @@ class SaleOrder(models.Model):
invoices = self.env['account.move'].search(query, order='invoice_date')
if invoices:
- if not self.env.user.is_leader and not self.env.user.is_sales_manager:
- due_extension = self.env['due.extension'].create([{
- 'partner_id': parent_id,
- 'day_extension': '3',
- 'order_id': self.id,
- }])
- due_extension.generate_due_line()
- self.due_id = due_extension.id
- if len(self.due_id.due_line) > 0:
- return True
- else:
- due_extension.unlink()
- return False
-
+ due_extension = self.env['due.extension'].create([{
+ 'partner_id': parent_id,
+ 'day_extension': '3',
+ 'order_id': self.id,
+ }])
+ due_extension.generate_due_line()
+ self.due_id = due_extension.id
+ if len(self.due_id.due_line) > 0:
+ return True
+ else:
+ due_extension.unlink()
+ return False
+
def _requires_approval_margin_leader(self):
return self.total_percent_margin <= 15 and not self.env.user.is_leader
-
+
def _requires_approval_margin_manager(self):
- return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader
+ return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.id == 375 and not self.env.user.is_leader
# return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager
-
+
def _create_approval_notification(self, approval_role):
title = 'Warning'
message = f'SO butuh approval {approval_role}'
return self._create_notification_action(title, message)
-
+
def _create_notification_action(self, title, message):
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
- 'params': { 'title': title, 'message': message, 'next': {'type': 'ir.actions.act_window_close'} },
+ 'params': {'title': title, 'message': message, 'next': {'type': 'ir.actions.act_window_close'}},
}
-
- def _set_sppkp_npwp_contact(self):
+
+ def _set_sppkp_npwp_contact(self):
partner = self.partner_id.parent_id or self.partner_id
if not partner.sppkp:
@@ -1551,7 +1673,7 @@ class SaleOrder(models.Model):
# partner.npwp = self.npwp
# partner.sppkp = self.sppkp
# partner.email = self.email
-
+
def _compute_total_margin(self):
for order in self:
total_margin = sum(line.item_margin for line in order.order_line if line.product_id)
@@ -1559,7 +1681,12 @@ class SaleOrder(models.Model):
total_margin -= order.ongkir_ke_xpdc
order.total_margin = total_margin
-
+
+ def _compute_total_before_margin(self):
+ for order in self:
+ total_before_margin = sum(line.item_before_margin for line in order.order_line if line.product_id)
+ order.total_before_margin = total_before_margin
+
def _compute_total_percent_margin(self):
for order in self:
if order.amount_untaxed == 0:
@@ -1569,9 +1696,10 @@ class SaleOrder(models.Model):
delivery_amt = order.delivery_amt
else:
delivery_amt = 0
-
+
# order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-delivery_amt-order.fee_third_party)) * 100, 2)
- order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-order.fee_third_party-order.biaya_lain_lain)) * 100, 2)
+ order.total_percent_margin = round(
+ (order.total_margin / (order.amount_untaxed - order.fee_third_party - order.biaya_lain_lain)) * 100, 2)
# order.total_percent_margin = round((order.total_margin / (order.amount_untaxed)) * 100, 2)
@api.onchange('sales_tax_id')
@@ -1594,20 +1722,20 @@ class SaleOrder(models.Model):
voucher = self.voucher_id
if voucher.limit > 0 and voucher.count_order >= voucher.limit:
raise UserError('Voucher tidak dapat digunakan karena sudah habis digunakan')
-
+
partner_voucher_orders = []
for order in voucher.order_ids:
if order.partner_id.id == self.partner_id.id:
partner_voucher_orders.append(order)
-
+
if voucher.limit_user > 0 and len(partner_voucher_orders) >= voucher.limit_user:
raise UserError('Voucher tidak dapat digunakan karena Customer ini sudah menghabiskan kuota voucher')
-
+
if self.pricelist_id.id in [x.id for x in voucher.excl_pricelist_ids]:
raise UserError('Voucher tidak dapat digunakan karena pricelist ini tidak berlaku pada voucher')
-
+
self.apply_voucher()
-
+
def action_apply_voucher_shipping(self):
for line in self.order_line:
if line.order_promotion_id:
@@ -1616,18 +1744,18 @@ class SaleOrder(models.Model):
voucher = self.voucher_shipping_id
if voucher.limit > 0 and voucher.count_order >= voucher.limit:
raise UserError('Voucher tidak dapat digunakan karena sudah habis digunakan')
-
+
partner_voucher_orders = []
for order in voucher.order_ids:
if order.partner_id.id == self.partner_id.id:
partner_voucher_orders.append(order)
-
+
if voucher.limit_user > 0 and len(partner_voucher_orders) >= voucher.limit_user:
raise UserError('Voucher tidak dapat digunakan karena Customer ini sudah menghabiskan kuota voucher')
-
+
if self.pricelist_id.id in [x.id for x in voucher.excl_pricelist_ids]:
raise UserError('Voucher tidak dapat digunakan karena pricelist ini tidak berlaku pada voucher')
-
+
self.apply_voucher_shipping()
def apply_voucher(self):
@@ -1644,7 +1772,7 @@ class SaleOrder(models.Model):
for line in self.order_line:
line.initial_discount = line.discount
-
+
voucher_type = voucher['type']
used_total = voucher['total'][voucher_type]
used_discount = voucher['discount'][voucher_type]
@@ -1660,11 +1788,11 @@ class SaleOrder(models.Model):
line_contribution = line.price_subtotal / used_total
line_voucher = used_discount * line_contribution
line_voucher_item = line_voucher / line.product_uom_qty
-
+
line_price_unit = line.price_unit / 1.11 if any(tax.id == 23 for tax in line.tax_id) else line.price_unit
line_discount_item = line_price_unit * line.discount / 100 + line_voucher_item
line_voucher_item = line_discount_item / line_price_unit * 100
-
+
line.amount_voucher_disc = line_voucher
line.discount = line_voucher_item
@@ -1675,27 +1803,27 @@ class SaleOrder(models.Model):
for order in self:
delivery_amt = order.delivery_amt
voucher = order.voucher_shipping_id
-
+
if voucher:
max_discount_amount = voucher.discount_amount
voucher_type = voucher.discount_type
-
+
if voucher_type == 'fixed_price':
discount = max_discount_amount
elif voucher_type == 'percentage':
discount = delivery_amt * (max_discount_amount / 100)
-
+
delivery_amt -= discount
-
+
delivery_amt = max(delivery_amt, 0)
-
+
order.delivery_amt = delivery_amt
-
+
order.amount_voucher_shipping_disc = discount
order.applied_voucher_shipping_id = order.voucher_id.id
def cancel_voucher(self):
- self.applied_voucher_id = False
+ self.applied_voucher_id = False
self.amount_voucher_disc = 0
for line in self.order_line:
line.amount_voucher_disc = 0
@@ -1704,17 +1832,18 @@ class SaleOrder(models.Model):
def cancel_voucher_shipping(self):
self.delivery_amt + self.amount_voucher_shipping_disc
- self.applied_voucher_shipping_id = False
+ self.applied_voucher_shipping_id = False
self.amount_voucher_shipping_disc = 0
def action_web_approve(self):
if self.env.uid != self.partner_id.user_id.id:
- raise UserError('You are not authorized to approve this order. Only %s can approve this order.' % self.partner_id.user_id.name)
-
+ raise UserError(
+ 'You are not authorized to approve this order. Only %s can approve this order.' % self.partner_id.user_id.name)
+
self.web_approval = 'company'
template = self.env.ref('indoteknik_custom.mail_template_sale_order_web_approve_notification')
template.send_mail(self.id, force_send=True)
-
+
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
@@ -1774,13 +1903,14 @@ class SaleOrder(models.Model):
if last_so and rec_purchase_price != last_so.purchase_price:
rec_taxes = self.env['account.tax'].search([('id', '=', rec_taxes_id)], limit=1)
if rec_taxes.price_include:
- selling_price = (rec_purchase_price / 1.11) / (1 - (last_so.item_percent_margin_without_deduction / 100))
+ selling_price = (rec_purchase_price / 1.11) / (
+ 1 - (last_so.item_percent_margin_without_deduction / 100))
else:
selling_price = rec_purchase_price / (1 - (last_so.item_percent_margin_without_deduction / 100))
tax_id = last_so.tax_id
for tax in tax_id:
if tax.price_include:
- selling_price = selling_price + (selling_price*11/100)
+ selling_price = selling_price + (selling_price * 11 / 100)
else:
selling_price = selling_price
discount = 0
@@ -1796,13 +1926,14 @@ class SaleOrder(models.Model):
elif last_so and rec_vendor_id == order_line.vendor_id.id and rec_purchase_price != last_so.purchase_price:
rec_taxes = self.env['account.tax'].search([('id', '=', rec_taxes_id)], limit=1)
if rec_taxes.price_include:
- selling_price = (rec_purchase_price / 1.11) / (1 - (last_so.item_percent_margin_without_deduction / 100))
+ selling_price = (rec_purchase_price / 1.11) / (
+ 1 - (last_so.item_percent_margin_without_deduction / 100))
else:
selling_price = rec_purchase_price / (1 - (last_so.item_percent_margin_without_deduction / 100))
tax_id = last_so.tax_id
for tax in tax_id:
if tax.price_include:
- selling_price = selling_price + (selling_price*11/100)
+ selling_price = selling_price + (selling_price * 11 / 100)
else:
selling_price = selling_price
discount = 0
@@ -1833,14 +1964,14 @@ class SaleOrder(models.Model):
return order
# def write(self, vals):
- # Call the super method to handle the write operation
- # res = super(SaleOrder, self).write(vals)
- # self._compute_etrts_date()
- # Check if the update is coming from a save operation
- # if any(field in vals for field in ['sppkp', 'npwp', 'email', 'customer_type']):
- # self._update_partner_details()
+ # Call the super method to handle the write operation
+ # res = super(SaleOrder, self).write(vals)
+ # self._compute_etrts_date()
+ # Check if the update is coming from a save operation
+ # if any(field in vals for field in ['sppkp', 'npwp', 'email', 'customer_type']):
+ # self._update_partner_details()
- # return res
+ # return res
def _update_partner_details(self):
for order in self:
@@ -1869,11 +2000,11 @@ class SaleOrder(models.Model):
if command[0] == 0: # A new line is being added
raise UserError(
"SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.")
-
+
res = super(SaleOrder, self).write(vals)
# self._check_total_margin_excl_third_party()
if any(fields in vals for fields in ['delivery_amt', 'carrier_id', 'shipping_cost_covered']):
self._validate_delivery_amt()
if any(field in vals for field in ["order_line", "client_order_ref"]):
- self._calculate_etrts_date()
- return res \ No newline at end of file
+ self._calculate_etrts_date()
+ return res
diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py
index aed95aab..9247d1c1 100644
--- a/indoteknik_custom/models/sale_order_line.py
+++ b/indoteknik_custom/models/sale_order_line.py
@@ -6,6 +6,7 @@ from datetime import datetime, timedelta
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
item_margin = fields.Float('Margin', compute='compute_item_margin', help="Total Margin in Sales Order Header")
+ item_before_margin = fields.Float('Before Margin', compute='compute_item_before_margin', help="Total Margin in Sales Order Header")
item_percent_margin = fields.Float('%Margin', compute='compute_item_margin', help="Total % Margin in Sales Order Header")
initial_discount = fields.Float('Initial Discount')
vendor_id = fields.Many2one(
@@ -40,6 +41,19 @@ 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')
+ def _get_outgoing_incoming_moves(self):
+ outgoing_moves = self.env['stock.move']
+ incoming_moves = self.env['stock.move']
+
+ for move in self.move_ids.filtered(lambda r: r.state != 'cancel' and not r.scrapped and self.product_id == r.product_id):
+ if move.location_dest_id.usage == "customer":
+ if not move.origin_returned_move_id or (move.origin_returned_move_id and move.to_refund):
+ outgoing_moves |= move
+ elif move.location_id.usage == "customer" and move.to_refund:
+ incoming_moves |= move
+
+ return outgoing_moves, incoming_moves
+
def _get_desc_updatable(self):
for line in self:
if line.product_id.id != 417724 and line.product_id.id:
@@ -146,6 +160,24 @@ class SaleOrderLine(models.Model):
if not line.margin_md:
line.margin_md = line.item_percent_margin
+ def compute_item_before_margin(self):
+ for line in self:
+ if not line.product_id or line.product_id.type == 'service' \
+ or line.price_unit <= 0 or line.product_uom_qty <= 0 \
+ or not line.vendor_id:
+ line.item_before_margin = 0
+ continue
+ # calculate margin without tax
+ sales_price = line.price_reduce_taxexcl * line.product_uom_qty
+
+ purchase_price = line.purchase_price
+ if line.purchase_tax_id.price_include:
+ purchase_price = line.purchase_price / 1.11
+
+ purchase_price = purchase_price * line.product_uom_qty
+ margin_per_item = sales_price - purchase_price
+ line.item_before_margin = margin_per_item
+
@api.onchange('vendor_id')
def onchange_vendor_id(self):
# TODO : need to change this logic @stephan
@@ -223,32 +255,33 @@ class SaleOrderLine(models.Model):
def _get_valid_purchase_price(self, purchase_price):
current_time = datetime.now()
delta_time = current_time - timedelta(days=365)
+ default_timestamp = datetime(1970, 1, 1, 0, 0, 0)
# delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S')
price = 0
- taxes = ''
+ taxes = 24
vendor_id = ''
human_last_update = purchase_price.human_last_update or datetime.min
system_last_update = purchase_price.system_last_update or datetime.min
-
- if purchase_price.taxes_product_id.type_tax_use == 'purchase':
- price = purchase_price.product_price
- taxes = purchase_price.taxes_product_id.id
+
+ # if purchase_price.taxes_product_id.type_tax_use == 'purchase':
+ price = purchase_price.product_price
+ taxes = purchase_price.taxes_product_id.id or 24
+ vendor_id = purchase_price.vendor_id.id
+ if delta_time > human_last_update:
+ price = 0
+ taxes = 24
+ vendor_id = ''
+
+ if system_last_update > human_last_update:
+ #if purchase_price.taxes_system_id.type_tax_use == 'purchase':
+ price = purchase_price.system_price
+ taxes = purchase_price.taxes_system_id.id or 24
vendor_id = purchase_price.vendor_id.id
- if delta_time > human_last_update:
+ if delta_time > system_last_update:
price = 0
- taxes = ''
+ taxes = 24
vendor_id = ''
-
- if system_last_update > human_last_update:
- if purchase_price.taxes_system_id.type_tax_use == 'purchase':
- price = purchase_price.system_price
- taxes = purchase_price.taxes_system_id.id
- vendor_id = purchase_price.vendor_id.id
- if delta_time > system_last_update:
- price = 0
- taxes = ''
- vendor_id = ''
return price, taxes, vendor_id
diff --git a/indoteknik_custom/models/shipment_group.py b/indoteknik_custom/models/shipment_group.py
index df3f1bb4..48a3fa21 100644
--- a/indoteknik_custom/models/shipment_group.py
+++ b/indoteknik_custom/models/shipment_group.py
@@ -14,6 +14,13 @@ class ShipmentGroup(models.Model):
number = fields.Char(string='Document No', index=True, copy=False, readonly=True, tracking=True)
shipment_line = fields.One2many('shipment.group.line', 'shipment_id', string='Shipment Group Lines', auto_join=True)
partner_id = fields.Many2one('res.partner', string='Customer')
+ carrier_id = fields.Many2one('delivery.carrier', string='Ekspedisi')
+ total_colly_line = fields.Float(string='Total Colly', compute='_compute_total_colly_line')
+
+ @api.depends('shipment_line.total_colly')
+ def _compute_total_colly_line(self):
+ for rec in self:
+ rec.total_colly_line = sum(rec.shipment_line.mapped('total_colly'))
@api.model
def create(self, vals):
@@ -35,6 +42,26 @@ class ShipmentGroupLine(models.Model):
('indoteknik', 'Indoteknik'),
('customer', 'Customer')
], string='Shipping Paid by', copy=False)
+ total_colly = fields.Float(string='Total Colly')
+ carrier_id = fields.Many2one('delivery.carrier', string='Ekspedisi')
+
+ @api.constrains('picking_id')
+ def _check_picking_id(self):
+ for rec in self:
+ if not rec.picking_id:
+ continue
+
+ duplicates = self.env['shipment.group.line'].search([
+ ('picking_id', '=', rec.picking_id.id),
+ ('id', '!=', rec.id)
+ ])
+
+ if duplicates:
+ shipment_numbers = duplicates.mapped('shipment_id.number')
+ raise UserError(
+ f"Picking {rec.picking_id.name} sudah discan dalam shipment group berikut: {', '.join(shipment_numbers)}! "
+ "Satu picking hanya boleh dimasukkan dalam satu shipment group."
+ )
@api.depends('picking_id.state')
def _compute_state(self):
@@ -60,14 +87,19 @@ class ShipmentGroupLine(models.Model):
if self.picking_id:
picking = self.env['stock.picking'].browse(self.picking_id.id)
- if self.shipment_id.partner_id and self.shipment_id.partner_id != picking.partner_id:
- raise UserError('Partner must be same as shipment group')
-
+ if self.shipment_id.carrier_id and self.shipment_id.carrier_id != picking.carrier_id:
+ raise UserError('carrier must be same as shipment group')
+
+ if picking.total_mapping_koli == 0:
+ raise UserError(f'Picking {picking.name} tidak memiliki mapping koli')
+
self.partner_id = picking.partner_id
self.shipping_paid_by = picking.sale_id.shipping_paid_by
+ self.carrier_id = picking.carrier_id.id
+ self.total_colly = picking.total_mapping_koli
- if not self.shipment_id.partner_id:
- self.shipment_id.partner_id = picking.partner_id
+ if not self.shipment_id.carrier_id:
+ self.shipment_id.carrier_id = picking.carrier_id
self.sale_id = picking.sale_id
diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py
index 20402c84..c4aefe19 100644
--- a/indoteknik_custom/models/solr/product_template.py
+++ b/indoteknik_custom/models/solr/product_template.py
@@ -100,7 +100,7 @@ class ProductTemplate(models.Model):
"product_rating_f": template.virtual_rating,
"product_id_i": template.id,
"image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id),
- "image_carousel_ss": carousel_images,
+ "image_carousel_ss": carousel_images if carousel_images else [],
'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id),
"variant_total_i": template.product_variant_count,
"stock_total_f": template.qty_stock_vendor,
diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py
index 3c765244..90ab30a4 100644
--- a/indoteknik_custom/models/stock_move.py
+++ b/indoteknik_custom/models/stock_move.py
@@ -14,6 +14,7 @@ class StockMove(models.Model):
qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant')
barcode = fields.Char(string='Barcode', related='product_id.barcode')
vendor_id = fields.Many2one('res.partner' ,string='Vendor')
+ hold_outgoingg = fields.Boolean('Hold Outgoing', default=False)
# @api.model_create_multi
# def create(self, vals_list):
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index ce1399fe..0fcb7ca1 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -87,7 +87,7 @@ class StockPicking(models.Model):
)
sj_documentation = fields.Binary(string="Dokumentasi Surat Jalan", )
paket_documentation = fields.Binary(string="Dokumentasi Paket", )
- sj_return_date = fields.Datetime(string="SJ Return Date", )
+ sj_return_date = fields.Datetime(string="SJ Return Date", copy=False)
responsible = fields.Many2one('res.users', string='Responsible', tracking=True)
approval_status = fields.Selection([
@@ -255,6 +255,13 @@ class StockPicking(models.Model):
lalamove_image_url = fields.Char(string="Lalamove Image URL")
lalamove_image_html = fields.Html(string="Lalamove Image", compute="_compute_lalamove_image_html")
+ # KGX Section
+ kgx_pod_photo_url = fields.Char('KGX Photo URL')
+ kgx_pod_photo = fields.Html('KGX Photo', compute='_compute_kgx_image_html')
+ kgx_pod_signature = fields.Char('KGX Signature URL')
+ kgx_pod_receive_time = fields.Datetime('KGX Ata Date')
+ kgx_pod_receiver = fields.Char('KGX Receiver')
+
total_koli = fields.Integer(compute='_compute_total_koli', string="Total Koli")
total_koli_display = fields.Char(compute='_compute_total_koli_display', string="Total Koli Display")
linked_out_picking_id = fields.Many2one('stock.picking', string="Linked BU/OUT", copy=False)
@@ -273,9 +280,78 @@ class StockPicking(models.Model):
state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')],
string='Packing Status')
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')
+ 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')
+ def _get_kgx_awb_number(self):
+ """Menggabungkan name dan origin untuk membuat AWB Number"""
+ self.ensure_one()
+ if not self.name or not self.origin:
+ return False
+ return f"{self.name} {self.origin}"
+
+ def _download_pod_photo(self, url):
+ """Mengunduh foto POD dari URL"""
+ try:
+ response = requests.get(url, timeout=10)
+ response.raise_for_status()
+ return base64.b64encode(response.content)
+ except Exception as e:
+ raise UserError(f"Gagal mengunduh foto POD: {str(e)}")
+
+ def _parse_datetime(self, dt_str):
+ """Parse datetime string dari format KGX"""
+ try:
+ from datetime import datetime
+ # Hilangkan timezone jika ada masalah parsing
+ if '+' in dt_str:
+ dt_str = dt_str.split('+')[0]
+ return datetime.strptime(dt_str, '%Y-%m-%dT%H:%M:%S')
+ except ValueError:
+ return False
+
+ def action_get_kgx_pod(self):
+ self.ensure_one()
+
+ awb_number = self._get_kgx_awb_number()
+ if not awb_number:
+ raise UserError("Nomor AWB tidak dapat dibuat, pastikan picking memiliki name dan origin")
+
+ url = "https://kgx.co.id/get_detail_awb"
+ headers = {'Content-Type': 'application/json'}
+ payload = {"params" : {'awb_number': awb_number}}
+
+ try:
+ response = requests.post(url, headers=headers, data=json.dumps(payload))
+ response.raise_for_status()
+ data = response.json()
+
+ if data.get('result', {}).get('data', []):
+ pod_data = data['result']['data'][0].get('connote_pod', {})
+ photo_url = pod_data.get('photo')
+
+ self.kgx_pod_photo_url = photo_url
+ self.kgx_pod_signature = pod_data.get('signature')
+ self.kgx_pod_receiver = pod_data.get('receiver')
+ self.kgx_pod_receive_time = self._parse_datetime(pod_data.get('timeReceive'))
+ self.driver_arrival_date = self._parse_datetime(pod_data.get('timeReceive'))
+
+ return data
+ else:
+ raise UserError(f"Tidak ditemukan data untuk AWB: {awb_number}")
+
+ except requests.exceptions.RequestException as e:
+ raise UserError(f"Gagal mengambil data POD: {str(e)}")
+
+ @api.constrains('sj_return_date')
+ def _check_sj_return_date(self):
+ for record in self:
+ if not record.driver_arrival_date:
+ if record.sj_return_date:
+ raise ValidationError(
+ _("Anda tidak dapat mengubah Tanggal Pengembalian setelah Tanggal Pengiriman!")
+ )
+
def _check_date_doc_kirim_modification(self):
for record in self:
if record.last_update_date_doc_kirim and not self.env.context.get('from_button_approve'):
@@ -445,6 +521,13 @@ class StockPicking(models.Model):
else:
record.lalamove_image_html = "No image available."
+ def _compute_kgx_image_html(self):
+ for record in self:
+ if record.kgx_pod_photo_url:
+ record.kgx_pod_photo = f'<img src="{record.kgx_pod_photo_url}" width="300" height="300"/>'
+ else:
+ record.kgx_pod_photo = "No image available."
+
def action_fetch_lalamove_order(self):
pickings = self.env['stock.picking'].search([
('picking_type_code', '=', 'outgoing'),
@@ -930,9 +1013,12 @@ class StockPicking(models.Model):
def action_assign(self):
res = super(StockPicking, self).action_assign()
- current_time = datetime.datetime.utcnow()
- self.real_shipping_id = self.sale_id.real_shipping_id
- self.date_availability = current_time
+ for move in self:
+ # if not move.sale_id.hold_outgoing and move.location_id.id != 57 and move.location_dest_id.id != 60:
+ # TODO cant skip hold outgoing cause of not singleton method
+ current_time = datetime.datetime.utcnow()
+ move.real_shipping_id = move.sale_id.real_shipping_id
+ move.date_availability = current_time
# self.check_state_reserve()
return res
@@ -973,6 +1059,8 @@ class StockPicking(models.Model):
if self.env.user.is_accounting:
pick.approval_return_status = 'approved'
continue
+ else:
+ pick.approval_return_status = 'pengajuan1'
action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_stock_return_note_wizard')
@@ -1237,12 +1325,12 @@ class StockPicking(models.Model):
continue
invoice = self.env['account.move'].search(
- [('sale_id', '=', picking.sale_id.id), ('state', 'not in', ['draft', 'cancel'])], limit=1)
+ [('sale_id', '=', picking.sale_id.id), ('state', 'not in', ['draft', 'cancel']), ('move_type', '=', 'out_invoice')], limit=1)
if not invoice:
continue
-
- if not picking.so_lama and (not picking.date_doc_kirim or not invoice.invoice_date):
+
+ if not picking.so_lama and invoice and (not picking.date_doc_kirim or not invoice.invoice_date):
raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!")
picking_date = fields.Date.to_date(picking.date_doc_kirim)
diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py
index 565b0315..aae09cc4 100644
--- a/indoteknik_custom/models/user_pengajuan_tempo_request.py
+++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py
@@ -365,13 +365,13 @@ class UserPengajuanTempoRequest(models.Model):
@api.onchange('tempo_duration')
def _tempo_duration_change(self):
for tempo in self:
- if tempo.env.user.id not in (7, 688, 28, 377, 12182):
+ if tempo.env.user.id not in (7, 688, 28, 377, 12182, 375):
raise UserError("Durasi tempo hanya bisa di ubah oleh Sales Manager atau Direktur")
@api.onchange('tempo_limit')
def _onchange_tempo_limit(self):
for tempo in self:
- if tempo.env.user.id not in (7, 688, 28, 377, 12182):
+ if tempo.env.user.id not in (7, 688, 28, 377, 12182, 375):
raise UserError("Limit tempo hanya bisa diubah oleh Sales Manager atau Direktur")
def button_approve(self):
for tempo in self:
@@ -381,7 +381,7 @@ class UserPengajuanTempoRequest(models.Model):
if tempo.env.user.id in (688, 28, 7):
raise UserError("Pengajuan tempo harus di approve oleh sales manager terlebih dahulu")
else:
- if tempo.env.user.id not in (377, 12182):
+ if tempo.env.user.id not in (377, 12182, 375):
# if tempo.env.user.id != 12182:
raise UserError("Pengajuan tempo hanya bisa di approve oleh sales manager")
else:
diff --git a/indoteknik_custom/models/voucher.py b/indoteknik_custom/models/voucher.py
index 7b458d01..b213a039 100644
--- a/indoteknik_custom/models/voucher.py
+++ b/indoteknik_custom/models/voucher.py
@@ -11,43 +11,47 @@ class Voucher(models.Model):
name = fields.Char(string='Name')
image = fields.Binary(string='Image')
code = fields.Char(string='Code', help='Kode voucher yang akan berlaku untuk pengguna')
+ voucher_category = fields.Many2many('product.public.category', string='Category Voucher',
+ help='Kategori Produk yang dapat menggunakan voucher ini')
description = fields.Text(string='Description')
discount_amount = fields.Float(string='Discount Amount')
- discount_type = fields.Selection(string='Discount Type',
- selection=[
- ('percentage', 'Percentage'),
- ('fixed_price', 'Fixed Price'),
- ],
- help='Select the type of discount:\n'
- '- Percentage: Persentase dari total harga.\n'
- '- Fixed Price: Jumlah tetap yang dikurangi dari harga total.'
- )
- visibility = fields.Selection(string='Visibility',
- selection=[
- ('public', 'Public'),
- ('private', 'Private')
- ],
- help='Select the visibility:\n'
- '- Public: Ditampilkan kepada seluruh pengguna.\n'
- '- Private: Tidak ditampilkan kepada seluruh pengguna.'
- )
+ discount_type = fields.Selection(string='Discount Type',
+ selection=[
+ ('percentage', 'Percentage'),
+ ('fixed_price', 'Fixed Price'),
+ ],
+ help='Select the type of discount:\n'
+ '- Percentage: Persentase dari total harga.\n'
+ '- Fixed Price: Jumlah tetap yang dikurangi dari harga total.'
+ )
+ visibility = fields.Selection(string='Visibility',
+ selection=[
+ ('public', 'Public'),
+ ('private', 'Private')
+ ],
+ help='Select the visibility:\n'
+ '- Public: Ditampilkan kepada seluruh pengguna.\n'
+ '- Private: Tidak ditampilkan kepada seluruh pengguna.'
+ )
start_time = fields.Datetime(string='Start Time')
end_time = fields.Datetime(string='End Time')
- min_purchase_amount = fields.Integer(string='Min. Purchase Amount', help='Nominal minimum untuk dapat menggunakan voucher. Isi 0 jika tidak ada minimum purchase amount')
+ min_purchase_amount = fields.Integer(string='Min. Purchase Amount',
+ help='Nominal minimum untuk dapat menggunakan voucher. Isi 0 jika tidak ada minimum purchase amount')
max_discount_amount = fields.Integer(string='Max. Discount Amount', help='Max nominal terhadap persentase diskon')
order_ids = fields.One2many('sale.order', 'applied_voucher_id', string='Order')
limit = fields.Integer(
- string='Limit',
+ string='Limit',
default=0,
help='Batas penggunaan voucher keseluruhan. Isi dengan angka 0 untuk penggunaan tanpa batas'
)
limit_user = fields.Integer(
- string='Limit User',
+ string='Limit User',
default=0,
help='Batas penggunaan voucher per pengguna. Misalnya, jika diisi dengan angka 1, maka setiap pengguna hanya dapat menggunakan voucher ini satu kali. Isi dengan angka 0 untuk penggunaan tanpa batas'
)
manufacture_ids = fields.Many2many('x_manufactures', string='Brands', help='Voucher appplied only for brand')
- excl_pricelist_ids = fields.Many2many('product.pricelist', string='Excluded Pricelists', help='Hide voucher from selected exclude pricelist')
+ excl_pricelist_ids = fields.Many2many('product.pricelist', string='Excluded Pricelists',
+ help='Hide voucher from selected exclude pricelist')
voucher_line = fields.One2many('voucher.line', 'voucher_id', 'Voucher Line')
terms_conditions = fields.Html('Terms and Conditions')
apply_type = fields.Selection(string='Apply Type', default="all", selection=[
@@ -64,12 +68,40 @@ class Voucher(models.Model):
('person', "Account Individu"),
('company', "Account Company"),
])
+
+ def is_voucher_applicable(self, product_id):
+ if not self.voucher_category:
+ return True
+
+ public_categories = product_id.public_categ_ids
+
+ return bool(set(public_categories.ids) & set(self.voucher_category.ids))
+
+ def is_voucher_applicable_for_category(self, category):
+ if not self.voucher_category:
+ return True
+
+ if category.id in self.voucher_category.ids:
+ return True
+
+ category_path = []
+ current_cat = category
+ while current_cat:
+ category_path.append(current_cat.id)
+ current_cat = current_cat.parent_id
+
+ for voucher_cat in self.voucher_category:
+ if voucher_cat.id in category_path:
+ return True
+
+ return False
+
@api.constrains('description')
def _check_description_length(self):
for record in self:
if record.description and len(record.description) > 120:
raise ValidationError('Deskripsi tidak boleh lebih dari 120 karakter')
-
+
@api.constrains('limit', 'limit_user')
def _check_limit(self):
for rec in self:
@@ -87,7 +119,7 @@ class Voucher(models.Model):
def res_format(self):
datas = [voucher.format() for voucher in self]
return datas
-
+
def format(self):
ir_attachment = self.env['ir.attachment']
data = {
@@ -100,7 +132,7 @@ class Voucher(models.Model):
'remaining_time': self._res_remaining_time(),
}
return data
-
+
def _res_remaining_time(self):
seconds = self._get_remaining_time()
remaining_time = timedelta(seconds=seconds)
@@ -116,14 +148,31 @@ class Voucher(models.Model):
time = minutes
unit = 'menit'
return f'{time} {unit}'
-
+
def _get_remaining_time(self):
calculate_time = self.end_time - datetime.now()
return round(calculate_time.total_seconds())
-
+
def filter_order_line(self, order_line):
+ # import logging
+ # _logger = logging.getLogger(__name__)
+
voucher_manufacture_ids = self.collect_manufacture_ids()
results = []
+
+ if self.voucher_category and len(order_line) > 0:
+ for line in order_line:
+ category_applicable = False
+ for category in line['product_id'].public_categ_ids:
+ if self.is_voucher_applicable_for_category(category):
+ category_applicable = True
+ break
+
+ if not category_applicable:
+ # _logger.info("Cart contains product %s with non-applicable category - voucher %s cannot be used",
+ # line['product_id'].name, self.code)
+ return []
+
for line in order_line:
manufacture_id = line['product_id'].x_manufacture.id or None
if self.apply_type == 'brand' and manufacture_id not in voucher_manufacture_ids:
@@ -132,35 +181,36 @@ class Voucher(models.Model):
product_flashsale = line['product_id']._get_active_flash_sale()
if len(product_flashsale) > 0:
continue
-
+
results.append(line)
-
+
return results
-
+
def calc_total_order_line(self, order_line):
- result = { 'all': 0, 'brand': {} }
+ result = {'all': 0, 'brand': {}}
for line in order_line:
manufacture_id = line['product_id'].x_manufacture.id or None
manufacture_total = result['brand'].get(manufacture_id, 0)
result['brand'][manufacture_id] = manufacture_total + line['subtotal']
result['all'] += line['subtotal']
-
+
return result
-
+
def calc_discount_amount(self, total):
- result = { 'all': 0, 'brand': {} }
+ result = {'all': 0, 'brand': {}}
if self.apply_type in ['all', 'shipping']:
if total['all'] < self.min_purchase_amount:
return result
-
+
if self.discount_type == 'percentage':
decimal_discount = self.discount_amount / 100
discount_all = total['all'] * decimal_discount
- result['all'] = min(discount_all, self.max_discount_amount) if self.max_discount_amount > 0 else discount_all
+ result['all'] = min(discount_all,
+ self.max_discount_amount) if self.max_discount_amount > 0 else discount_all
else:
result['all'] = min(self.discount_amount, total['all'])
-
+
return result
for line in self.voucher_line:
@@ -173,99 +223,129 @@ class Voucher(models.Model):
elif line.discount_type == 'percentage':
decimal_discount = line.discount_amount / 100
discount_brand = total_brand * decimal_discount
- discount_brand = min(discount_brand, line.max_discount_amount) if line.max_discount_amount > 0 else discount_brand
+ discount_brand = min(discount_brand,
+ line.max_discount_amount) if line.max_discount_amount > 0 else discount_brand
else:
discount_brand = min(line.discount_amount, total_brand)
-
+
result['brand'][manufacture_id] = round(discount_brand, 2)
result['all'] += discount_brand
-
+
result['all'] = round(result['all'], 2)
return result
def apply(self, order_line):
- order_line = self.filter_order_line(order_line)
- amount_total = self.calc_total_order_line(order_line)
+
+ filtered_order_line = self.filter_order_line(order_line)
+
+ amount_total = self.calc_total_order_line(filtered_order_line)
+
discount = self.calc_discount_amount(amount_total)
+
return {
'discount': discount,
'total': amount_total,
'type': self.apply_type,
- 'valid_order': order_line
+ 'valid_order': filtered_order_line,
}
-
+
def collect_manufacture_ids(self):
return [x.manufacture_id.id for x in self.voucher_line]
-
+
def calculate_discount(self, price):
if price < self.min_purchase_amount:
return 0
-
+
if self.discount_type == 'fixed_price':
return self.discount_amount
-
+
if self.discount_type == 'percentage':
discount = price * self.discount_amount / 100
max_disc = self.max_discount_amount
return discount if max_disc == 0 else min(discount, max_disc)
-
+
return 0
-
+
def get_active_voucher(self, domain):
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
domain += [
('start_time', '<=', current_time),
('end_time', '>=', current_time),
]
- vouchers = self.search(domain, order='min_purchase_amount ASC')
+ vouchers = self.search(domain, order='min_purchase_amount ASC')
return vouchers
-
+
def generate_tnc(self):
+ def format_currency(amount):
+ formatted_number = '{:,.0f}'.format(amount).replace(',', '.')
+ return f'Rp{formatted_number}'
+
tnc = []
tnc.append('<ol>')
- tnc.append('<li>Voucher hanya berlaku apabila pembelian Pengguna sudah memenuhi syarat dan ketentuan yang tertera pada voucher</li>')
+ tnc.append(
+ '<li>Voucher hanya berlaku apabila pembelian Pengguna sudah memenuhi syarat dan ketentuan yang tertera pada voucher</li>')
tnc.append(f'<li>Voucher berlaku {self._res_remaining_time()} lagi</li>')
tnc.append(f'<li>Voucher tidak bisa digunakan apabila terdapat produk flash sale</li>')
- if len(self.voucher_line) > 0:
- brand_names = ', '.join([x.manufacture_id.x_name or '' for x in self.voucher_line])
- tnc.append(f'<li>Voucher berlaku untuk produk dari brand {brand_names}</li>')
- tnc.append(self.generate_detail_tnc())
+
+ if self.apply_type == 'brand':
+ tnc.append(f'<li>Voucher berlaku untuk produk dari brand terpilih</li>')
+ tnc.append(
+ f'<li>Nominal potongan produk yang bisa didapatkan hingga 10 Juta dengan minimum pembelian 10 Ribu.</li>')
+ elif self.apply_type == 'all' or self.apply_type == 'shipping':
+ if self.voucher_category:
+ category_names = ', '.join([cat.name for cat in self.voucher_category])
+ tnc.append(
+ f'<li>Voucher hanya berlaku untuk produk dalam kategori {category_names} dan sub-kategorinya</li>')
+ tnc.append(
+ f'<li>Voucher tidak dapat digunakan jika ada produk di keranjang yang tidak termasuk dalam kategori tersebut</li>')
+
+ if self.discount_type == 'percentage' and self.apply_type != 'brand':
+ tnc.append(
+ f'<li>Nominal potongan produk yang bisa didapatkan sebesar {self.max_discount_amount}% dengan minimum pembelian {self.min_purchase_amount}</li>')
+ elif self.discount_type == 'percentage' and self.apply_type != 'brand':
+ tnc.append(
+ f'<li>Nominal potongan produk yang bisa didapatkan sebesar {format_currency(self.discount_amount)} dengan minimum pembelian {format_currency(self.min_purchase_amount)}</li>')
+
tnc.append('</ol>')
-
- return ' '.join(tnc)
-
- def generate_detail_tnc(self):
- def format_currency(amount):
- formatted_number = '{:,.0f}'.format(amount).replace(',', '.')
- return f'Rp{formatted_number}'
-
- tnc = []
- if self.apply_type == 'all':
- tnc.append('<li>')
- tnc.append('Nominal potongan yang bisa didapatkan sebesar')
- tnc.append(f'{self.discount_amount}%' if self.discount_type == 'percentage' else format_currency(self.discount_amount))
-
- if self.discount_type == 'percentage' and self.max_discount_amount > 0:
- tnc.append(f'hingga {format_currency(self.max_discount_amount)}')
-
- tnc.append(f'dengan minimum pembelian {format_currency(self.min_purchase_amount)}' if self.min_purchase_amount > 0 else 'tanpa minimum pembelian')
- tnc.append('</li>')
- else:
- for line in self.voucher_line:
- line_tnc = []
- line_tnc.append(f'Nominal potongan produk {line.manufacture_id.x_name} yang bisa didapatkan sebesar')
- line_tnc.append(f'{line.discount_amount}%' if line.discount_type == 'percentage' else format_currency(line.discount_amount))
-
- if line.discount_type == 'percentage' and line.max_discount_amount > 0:
- line_tnc.append(f'hingga {format_currency(line.max_discount_amount)}')
-
- line_tnc.append(f'dengan minimum pembelian {format_currency(line.min_purchase_amount)}' if line.min_purchase_amount > 0 else 'tanpa minimum pembelian')
- line_tnc = ' '.join(line_tnc)
- tnc.append(f'<li>{line_tnc}</li>')
+ # tnc.append(self.generate_detail_tnc())
return ' '.join(tnc)
- # copy semua data kalau diduplicate
+ # def generate_detail_tnc(self):
+ # def format_currency(amount):
+ # formatted_number = '{:,.0f}'.format(amount).replace(',', '.')
+ # return f'Rp{formatted_number}'
+ #
+ # tnc = []
+ # if self.apply_type == 'all':
+ # tnc.append('<li>')
+ # tnc.append('Nominal potongan yang bisa didapatkan sebesar')
+ # tnc.append(f'{self.discount_amount}%' if self.discount_type == 'percentage' else format_currency(
+ # self.discount_amount))
+ #
+ # if self.discount_type == 'percentage' and self.max_discount_amount > 0:
+ # tnc.append(f'hingga {format_currency(self.max_discount_amount)}')
+ #
+ # tnc.append(
+ # f'dengan minimum pembelian {format_currency(self.min_purchase_amount)}' if self.min_purchase_amount > 0 else 'tanpa minimum pembelian')
+ # tnc.append('</li>')
+ # else:
+ # for line in self.voucher_line:
+ # line_tnc = []
+ # line_tnc.append(f'Nominal potongan produk {line.manufacture_id.x_name} yang bisa didapatkan sebesar')
+ # line_tnc.append(f'{line.discount_amount}%' if line.discount_type == 'percentage' else format_currency(
+ # line.discount_amount))
+ #
+ # if line.discount_type == 'percentage' and line.max_discount_amount > 0:
+ # line_tnc.append(f'hingga {format_currency(line.max_discount_amount)}')
+ #
+ # line_tnc.append(
+ # f'dengan minimum pembelian {format_currency(line.min_purchase_amount)}' if line.min_purchase_amount > 0 else 'tanpa minimum pembelian')
+ # line_tnc = ' '.join(line_tnc)
+ # tnc.append(f'<li>{line_tnc}</li>')
+ # return ' '.join(tnc)
+
+ # copy semua data kalau diduplicate
def copy(self, default=None):
default = dict(default or {})
voucher_lines = []
@@ -280,4 +360,4 @@ class Voucher(models.Model):
}))
default['voucher_line'] = voucher_lines
- return super(Voucher, self).copy(default) \ No newline at end of file
+ return super(Voucher, self).copy(default)
diff --git a/indoteknik_custom/models/website_user_cart.py b/indoteknik_custom/models/website_user_cart.py
index 44393cf1..a6d08949 100644
--- a/indoteknik_custom/models/website_user_cart.py
+++ b/indoteknik_custom/models/website_user_cart.py
@@ -1,10 +1,11 @@
from odoo import fields, models, api
from datetime import datetime, timedelta
+
class WebsiteUserCart(models.Model):
_name = 'website.user.cart'
_rec_name = 'user_id'
-
+
user_id = fields.Many2one('res.users', string='User')
product_id = fields.Many2one('product.product', string='Product')
program_line_id = fields.Many2one('promotion.program.line', string='Program', help="Apply program")
@@ -18,7 +19,8 @@ class WebsiteUserCart(models.Model):
is_reminder = fields.Boolean(string='Reminder?')
phone_user = fields.Char(string='Phone', related='user_id.mobile')
price = fields.Float(string='Price', compute='_compute_price')
- program_product_id = fields.Many2one('product.product', string='Program Products', compute='_compute_program_product_ids')
+ program_product_id = fields.Many2one('product.product', string='Program Products',
+ compute='_compute_program_product_ids')
@api.depends('program_line_id')
def _compute_program_product_ids(self):
@@ -55,6 +57,12 @@ class WebsiteUserCart(models.Model):
product = self.product_id.v2_api_single_response(self.product_id)
res.update(product)
+ # Add category information
+ res['categories'] = [{
+ 'id': cat.id,
+ 'name': cat.name
+ } for cat in self.product_id.public_categ_ids]
+
# Check if the product's inventory location is in ID 57 or 83
target_locations = [57, 83]
stock_quant = self.env['stock.quant'].search([
@@ -90,7 +98,14 @@ class WebsiteUserCart(models.Model):
def get_products(self):
products = [x.get_product() for x in self]
-
+
+ for i, cart_item in enumerate(self):
+ if cart_item.product_id and i < len(products):
+ products[i]['categories'] = [{
+ 'id': cat.id,
+ 'name': cat.name
+ } for cat in cart_item.product_id.public_categ_ids]
+
return products
def get_product_by_user(self, user_id, selected=False, source=False):
@@ -121,10 +136,10 @@ class WebsiteUserCart(models.Model):
products = products_active.get_products()
return products
-
+
def get_user_checkout(self, user_id, voucher=False, voucher_shipping=False, source=False):
products = self.get_product_by_user(user_id=user_id, selected=True, source=source)
-
+
total_purchase = 0
total_discount = 0
for product in products:
@@ -132,9 +147,9 @@ class WebsiteUserCart(models.Model):
price = product['package_price'] * product['quantity']
else:
price = product['price']['price'] * product['quantity']
-
+
discount_price = price - product['price']['price_discount'] * product['quantity']
-
+
total_purchase += price
total_discount += discount_price
@@ -142,7 +157,7 @@ class WebsiteUserCart(models.Model):
discount_voucher = 0
discount_voucher_shipping = 0
order_line = []
-
+
if voucher or voucher_shipping:
for product in products:
if product['cart_type'] == 'promotion': continue
@@ -153,16 +168,16 @@ class WebsiteUserCart(models.Model):
'qty': product['quantity'],
'subtotal': product['subtotal']
})
-
+
if voucher:
voucher_info = voucher.apply(order_line)
discount_voucher = voucher_info['discount']['all']
subtotal -= discount_voucher
-
+
if voucher_shipping:
voucher_shipping_info = voucher_shipping.apply(order_line)
- discount_voucher_shipping = voucher_shipping_info['discount']['all']
-
+ discount_voucher_shipping = voucher_shipping_info['discount']['all']
+
tax = round(subtotal * 0.11)
grand_total = subtotal + tax
total_weight = sum(x['weight'] * x['quantity'] for x in products)
@@ -179,28 +194,31 @@ class WebsiteUserCart(models.Model):
'kg': total_weight,
'g': total_weight * 1000
},
- 'has_product_without_weight': any(not product.get('weight') or product.get('weight') == 0 for product in products),
+ 'has_product_without_weight': any(
+ not product.get('weight') or product.get('weight') == 0 for product in products),
'products': products
}
return result
-
+
def action_mail_reminder_to_checkout(self, limit=200):
user_ids = self.search([('is_reminder', '=', False)]).mapped('user_id')[:limit]
-
+
for user in user_ids:
latest_cart = self.search([('user_id', '=', user.id)], order='create_date desc', limit=1)
-
+
carts_to_remind = self.search([('user_id', '=', user.id)])
-
+
if latest_cart and not latest_cart.is_reminder:
for cart in carts_to_remind:
check = cart.check_product_flashsale(cart.product_id.id)
- if not cart.program_line_id and cart.product_id.default_code and not 'BOM' in cart.product_id.default_code and check['is_flashsale'] == False:
+ if not cart.program_line_id and cart.product_id.default_code and not 'BOM' in cart.product_id.default_code and \
+ check['is_flashsale'] == False:
cart.is_selected = True
- if cart.program_line_id or check['is_flashsale'] or cart.product_id.default_code and 'BOM' in cart.product_id.default_code:
+ if cart.program_line_id or check[
+ 'is_flashsale'] or cart.product_id.default_code and 'BOM' in cart.product_id.default_code:
cart.is_selected = False
cart.is_reminder = True
-
+
template = self.env.ref('indoteknik_custom.mail_template_user_cart_reminder_to_checkout')
template.send_mail(latest_cart.id, force_send=True)
@@ -234,8 +252,9 @@ class WebsiteUserCart(models.Model):
break
product_discount = subtotal_promo if cart.program_line_id or check['is_flashsale'] else subtotal
- total_discount += product_discount
- if check['is_flashsale'] == False and cart.product_id.default_code and not 'BOM' in cart.product_id.default_code:
+ total_discount += product_discount
+ if check[
+ 'is_flashsale'] == False and cart.product_id.default_code and not 'BOM' in cart.product_id.default_code:
voucher_product = subtotal * (discount_amount / 100.0)
total_voucher += voucher_product
@@ -253,14 +272,15 @@ class WebsiteUserCart(models.Model):
def check_product_flashsale(self, product_id):
product = product_id
current_time = datetime.utcnow()
- found_product = self.env['product.pricelist.item'].search([('product_id', '=', product_id), ('pricelist_id.is_flash_sale', '=', True)])
+ found_product = self.env['product.pricelist.item'].search(
+ [('product_id', '=', product_id), ('pricelist_id.is_flash_sale', '=', True)])
if found_product:
for found in found_product:
pricelist_flashsale = found.pricelist_id
if pricelist_flashsale.start_date <= current_time <= pricelist_flashsale.end_date:
- return {
+ return {
'is_flashsale': True,
'price': found.fixed_price
}
@@ -269,10 +289,9 @@ class WebsiteUserCart(models.Model):
'is_flashsale': False
}
- return {
+ return {
'is_flashsale': False
}
-
# if found_product:
# start_date = found_product.pricelist_id.start_date
@@ -291,26 +310,26 @@ class WebsiteUserCart(models.Model):
# return {
# 'is_flashsale': False
# }
-
+
def get_data_promo(self, program_line_id):
program_line_product = self.env['promotion.product'].search([
('program_line_id', '=', program_line_id)
- ])
-
+ ])
+
program_free_product = self.env['promotion.free_product'].search([
('program_line_id', '=', program_line_id)
- ])
+ ])
return program_line_product, program_free_product
-
+
def get_weight_product(self, program_line_id):
program_line_product = self.env['promotion.product'].search([
('program_line_id', '=', program_line_id)
- ])
-
+ ])
+
program_free_product = self.env['promotion.free_product'].search([
('program_line_id', '=', program_line_id)
- ])
-
+ ])
+
real_weight = 0.0
if program_line_product:
for product in program_line_product:
@@ -321,16 +340,16 @@ class WebsiteUserCart(models.Model):
real_weight += product.product_id.weight
return real_weight
-
+
def get_price_coret(self, program_line_id):
program_line_product = self.env['promotion.product'].search([
('program_line_id', '=', program_line_id)
- ])
-
+ ])
+
program_free_product = self.env['promotion.free_product'].search([
('program_line_id', '=', program_line_id)
- ])
-
+ ])
+
price_coret = 0.0
for product in program_line_product:
price = self.get_price_website(product.product_id.id)
@@ -340,20 +359,22 @@ class WebsiteUserCart(models.Model):
price = self.get_price_website(product.product_id.id)
price_coret += price['price'] * product.qty
- return price_coret
-
+ return price_coret
+
def get_price_website(self, product_id):
- price_website = self.env['product.pricelist.item'].search([('product_id', '=', product_id), ('pricelist_id', '=', 17022)], limit=1)
-
- price_tier = self.env['product.pricelist.item'].search([('product_id', '=', product_id), ('pricelist_id', '=', 17023)], limit=1)
-
+ price_website = self.env['product.pricelist.item'].search(
+ [('product_id', '=', product_id), ('pricelist_id', '=', 17022)], limit=1)
+
+ price_tier = self.env['product.pricelist.item'].search(
+ [('product_id', '=', product_id), ('pricelist_id', '=', 17023)], limit=1)
+
fixed_price = price_website.fixed_price if price_website else 0.0
discount = price_tier.price_discount if price_tier else 0.0
-
+
discounted_price = fixed_price - (fixed_price * discount / 100)
-
+
final_price = discounted_price / 1.11
-
+
return {
'price': final_price,
'web_price': discounted_price
@@ -365,4 +386,4 @@ class WebsiteUserCart(models.Model):
def format_currency(self, number):
number = int(number)
- return "{:,}".format(number).replace(',', '.') \ No newline at end of file
+ return "{:,}".format(number).replace(',', '.')
diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv
index 7d7c98f4..601f04c5 100755
--- a/indoteknik_custom/security/ir.model.access.csv
+++ b/indoteknik_custom/security/ir.model.access.csv
@@ -180,3 +180,4 @@ access_reject_reason_commision,reject.reason.commision,model_reject_reason_commi
access_shipping_option,shipping.option,model_shipping_option,,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
diff --git a/indoteknik_custom/views/account_move.xml b/indoteknik_custom/views/account_move.xml
index 17263c3a..46737a40 100644
--- a/indoteknik_custom/views/account_move.xml
+++ b/indoteknik_custom/views/account_move.xml
@@ -92,6 +92,7 @@
<field name="is_efaktur_exported" optional="hide"/>
<field name="invoice_day_to_due" attrs="{'invisible': [['payment_state', 'in', ('paid', 'in_payment', 'reversed')]]}" optional="hide"/>
<field name="new_invoice_day_to_due" attrs="{'invisible': [['payment_state', 'in', ('paid', 'in_payment', 'reversed')]]}" optional="hide"/>
+ <field name="length_of_payment" optional="hide"/>
<field name="mark_upload_efaktur" optional="hide" widget="badge"
decoration-danger="mark_upload_efaktur == 'belum_upload'"
decoration-success="mark_upload_efaktur == 'sudah_upload'" />
diff --git a/indoteknik_custom/views/barcoding_product.xml b/indoteknik_custom/views/barcoding_product.xml
index 55876580..b259f1e8 100644
--- a/indoteknik_custom/views/barcoding_product.xml
+++ b/indoteknik_custom/views/barcoding_product.xml
@@ -20,6 +20,7 @@
<tree>
<field name="product_id"/>
<field name="qr_code_variant" widget="image"/>
+ <field name="sequence_with_total" attrs="{'invisible': [['parent.type', 'not in', ('multiparts')]]}"/>
</tree>
</field>
</record>
@@ -35,8 +36,8 @@
<field name="product_id" required="1"/>
<field name="type" required="1"/>
<field name="quantity" attrs="{'invisible': [['type', 'in', ('barcoding','barcoding_box')]], 'required': [['type', 'not in', ('barcoding')]]}"/>
- <field name="barcode" attrs="{'invisible': [['type', 'in', ('print')]], 'required': [['type', 'not in', ('print')]]}"/>
- <field name="qty_pcs_box" attrs="{'invisible': [['type', 'in', ('print','barcoding')]], 'required': [['type', 'not in', ('print', 'barcoding')]]}"/>
+ <field name="barcode" attrs="{'invisible': [['type', 'in', ('print','multiparts')]], 'required': [['type', 'not in', ('print','multiparts')]]}"/>
+ <field name="qty_pcs_box" attrs="{'invisible': [['type', 'in', ('print','barcoding','multiparts')]], 'required': [['type', 'not in', ('print','barcoding','multiparts')]]}"/>
</group>
</group>
<notebook>
diff --git a/indoteknik_custom/views/customer_commision.xml b/indoteknik_custom/views/customer_commision.xml
index 9f0e1e8a..37df16ff 100644
--- a/indoteknik_custom/views/customer_commision.xml
+++ b/indoteknik_custom/views/customer_commision.xml
@@ -11,11 +11,12 @@
<field name="partner_ids" widget="many2many_tags"/>
<field name="commision_percent"/>
<field name="commision_amt" readonly="1"/>
- <field name="status" readonly="1" decoration-success="status == 'approved'" widget="badge" optional="show"/>
+ <field name="status" readonly="1" decoration-success="status == 'approved'" widget="badge"
+ optional="show"/>
<field name="payment_status" readonly="1"
- decoration-success="payment_status == 'payment'"
- decoration-danger="payment_status == 'pending'"
- widget="badge"/>
+ decoration-success="payment_status == 'payment'"
+ decoration-danger="payment_status == 'pending'"
+ widget="badge"/>
<field name="brand_ids" widget="many2many_tags"/>
<field name="grouped_so_number" readonly="1" optional="hide"/>
<field name="grouped_invoice_number" readonly="1" optional="hide"/>
@@ -47,89 +48,89 @@
<field name="model">customer.commision</field>
<field name="arch" type="xml">
<form>
-<!-- attrs="{'invisible': [('status', 'in', ['draft','pengajuan1','pengajuan2','pengajuan3','pengajuan4'])]}"-->
+ <!-- attrs="{'invisible': [('status', 'in', ['draft','pengajuan1','pengajuan2','pengajuan3','pengajuan4'])]}"-->
<header>
<button name="action_confirm_customer_commision"
- string="Confirm" type="object"
- attrs="{'invisible': [('status', 'in', ['approved','reject'])]}"
- options="{}"/>
+ string="Confirm" type="object"
+ attrs="{'invisible': [('status', 'in', ['approved','reject'])]}"
+ options="{}"/>
<button name="action_reject"
- string="Reject"
- attrs="{'invisible': [('status', 'in', ['approved','reject'])]}"
- type="object"/>
- <button name="button_draft"
- string="Reset to Draft"
- attrs="{'invisible': [('status', '!=', 'reject')]}"
- type="object"/>
+ string="Reject"
+ attrs="{'invisible': [('status', 'in', ['approved','reject'])]}"
+ type="object"/>
+ <button name="button_draft"
+ string="Reset to Draft"
+ attrs="{'invisible': [('status', '!=', 'reject')]}"
+ type="object"/>
<button name="action_confirm_customer_payment"
- string="Konfirmasi Pembayaran" type="object"
- options="{}"
- attrs="{'invisible': [('payment_status', '==', 'payment')], 'readonly': [('payment_status', '=', 'payment')]}"/>
+ string="Konfirmasi Pembayaran" type="object"
+ options="{}"
+ attrs="{'invisible': [('payment_status', '==', 'payment')], 'readonly': [('payment_status', '=', 'payment')]}"/>
<field name="status" widget="statusbar"
- statusbar_visible="draft,pengajuan1,pengajuan2,pengajuan3,pengajuan4,approved"
- statusbar_colors='{"reject":"red"}'/>
+ statusbar_visible="draft,pengajuan1,pengajuan2,pengajuan3,pengajuan4,approved"
+ statusbar_colors='{"reject":"red"}'/>
</header>
<sheet string="Customer Commision">
- <div class="oe_button_box" name="button_box"/>
+ <div class="oe_button_box" name="button_box"/>
+ <group>
<group>
+ <field name="number"/>
+ <field name="date_from"/>
+ <field name="partner_ids" widget="many2many_tags"/>
+ <field name="description"/>
+ <field name="commision_percent"/>
+ <field name="commision_amt"/>
+ <field name="commision_amt_text"/>
+ <field name="grouped_so_number" readonly="1"/>
+ <field name="grouped_invoice_number" readonly="1"/>
+ <field name="approved_by" readonly="1"/>
+ </group>
+ <group>
+ <div>
+ <button name="generate_customer_commision"
+ string="Generate Line"
+ type="object"
+ class="mr-2 oe_highlight"
+ />
+ </div>
+ <field name="date_to"/>
+ <field name="sales_id"/>
+ <field name="commision_type"/>
+ <field name="brand_ids" widget="many2many_tags"/>
+ <field name="notification" readonly="1"/>
+ <!-- <field name="status" readonly="1"/>-->
+ <field name="payment_status" readonly="1"/>
+ <field name="total_dpp"/>
+ </group>
+ </group>
+ <notebook>
+ <page string="Lines">
+ <field name="commision_lines"/>
+ </page>
+ <page string="Other Info" name="customer_commision_info">
<group>
- <field name="number"/>
- <field name="date_from"/>
- <field name="partner_ids" widget="many2many_tags"/>
- <field name="description"/>
- <field name="commision_percent"/>
- <field name="commision_amt"/>
- <field name="commision_amt_text"/>
- <field name="grouped_so_number" readonly="1"/>
- <field name="grouped_invoice_number" readonly="1"/>
- <field name="approved_by" readonly="1"/>
+ <field name="bank_name"/>
+ <field name="account_name"/>
+ <field name="bank_account"/>
+ <field name="note_transfer"/>
</group>
+ </page>
+ <page string="Finance Notes">
<group>
- <div>
- <button name="generate_customer_commision"
- string="Generate Line"
- type="object"
- class="mr-2 oe_highlight"
- />
- </div>
- <field name="date_to"/>
- <field name="sales_id"/>
- <field name="commision_type"/>
- <field name="brand_ids" widget="many2many_tags"/>
- <field name="notification" readonly="1"/>
-<!-- <field name="status" readonly="1"/>-->
- <field name="payment_status" readonly="1" />
- <field name="total_dpp"/>
+ <field name="note_finnance"/>
</group>
- </group>
- <notebook>
- <page string="Lines">
- <field name="commision_lines"/>
- </page>
- <page string="Other Info" name="customer_commision_info">
- <group>
- <field name="bank_name"/>
- <field name="account_name"/>
- <field name="bank_account"/>
- <field name="note_transfer"/>
- </group>
- </page>
- <page string="Finance Notes">
- <group>
- <field name="note_finnance"/>
- </group>
- </page>
- </notebook>
- </sheet>
- <div class="oe_chatter">
- <field name="message_follower_ids" widget="mail_followers"/>
- <field name="message_ids" widget="mail_thread"/>
- </div>
+ </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>
- <!-- Wizard for Reject Reason -->
+ <!-- Wizard for Reject Reason -->
<record id="view_reject_reason_wizard_form" model="ir.ui.view">
<field name="name">reject.reason.commision.form</field>
<field name="model">reject.reason.commision</field>
@@ -160,7 +161,12 @@
<field name="arch" type="xml">
<search string="Search Customer Commision">
<field name="partner_ids"/>
- </search>
+ <group expand="0" string="Group By">
+ <filter string="Partner" name="group_partner"
+ domain="[]"
+ context="{'group_by':'partner_ids'}"/>
+ </group>
+ </search>
</field>
</record>
@@ -173,17 +179,17 @@
</record>
<menuitem id="menu_customer_commision_acct"
- name="Customer Commision"
- action="customer_commision_action"
- parent="account.menu_finance_entries"
- sequence="113"
+ name="Customer Commision"
+ action="customer_commision_action"
+ parent="account.menu_finance_entries"
+ sequence="113"
/>
<menuitem id="menu_customer_commision_sales"
- name="Customer Commision"
- action="customer_commision_action"
- parent="sale.product_menu_catalog"
- sequence="101"
+ name="Customer Commision"
+ action="customer_commision_action"
+ parent="sale.product_menu_catalog"
+ sequence="101"
/>
<record id="customer_rebate_tree" model="ir.ui.view">
@@ -217,34 +223,34 @@
<field name="arch" type="xml">
<form>
<sheet string="Customer Rebate">
- <div class="oe_button_box" name="button_box"/>
+ <div class="oe_button_box" name="button_box"/>
+ <group>
<group>
- <group>
- <field name="date_from"/>
- <field name="partner_id"/>
- <field name="target_1st"/>
- <field name="target_2nd"/>
- <field name="dpp_q1"/>
- <field name="dpp_q2"/>
- <field name="dpp_q3"/>
- <field name="dpp_q4"/>
- </group>
- <group>
- <field name="date_to"/>
- <field name="description"/>
- <field name="achieve_1"/>
- <field name="achieve_2"/>
- <field name="status_q1"/>
- <field name="status_q2"/>
- <field name="status_q3"/>
- <field name="status_q4"/>
- </group>
+ <field name="date_from"/>
+ <field name="partner_id"/>
+ <field name="target_1st"/>
+ <field name="target_2nd"/>
+ <field name="dpp_q1"/>
+ <field name="dpp_q2"/>
+ <field name="dpp_q3"/>
+ <field name="dpp_q4"/>
+ </group>
+ <group>
+ <field name="date_to"/>
+ <field name="description"/>
+ <field name="achieve_1"/>
+ <field name="achieve_2"/>
+ <field name="status_q1"/>
+ <field name="status_q2"/>
+ <field name="status_q3"/>
+ <field name="status_q4"/>
</group>
- </sheet>
- <div class="oe_chatter">
- <field name="message_follower_ids" widget="mail_followers"/>
- <field name="message_ids" widget="mail_thread"/>
- </div>
+ </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>
@@ -257,16 +263,16 @@
</record>
<menuitem id="menu_customer_rebate_acct"
- name="Customer Rebate"
- action="customer_rebate_action"
- parent="account.menu_finance_entries"
- sequence="114"
+ name="Customer Rebate"
+ action="customer_rebate_action"
+ parent="account.menu_finance_entries"
+ sequence="114"
/>
<menuitem id="menu_customer_rebate_sales"
- name="Customer Rebate"
- action="customer_rebate_action"
- parent="sale.product_menu_catalog"
- sequence="102"
+ name="Customer Rebate"
+ action="customer_rebate_action"
+ parent="sale.product_menu_catalog"
+ sequence="102"
/>
</odoo> \ No newline at end of file
diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml
index 9f980751..97bf40bb 100644
--- a/indoteknik_custom/views/ir_sequence.xml
+++ b/indoteknik_custom/views/ir_sequence.xml
@@ -65,7 +65,7 @@
<field name="name">Shipment Group</field>
<field name="code">shipment.group</field>
<field name="active">TRUE</field>
- <field name="prefix">SG/%(year)s/</field>
+ <field name="prefix">SGR/%(year)s/</field>
<field name="padding">5</field>
<field name="number_next">1</field>
<field name="number_increment">1</field>
diff --git a/indoteknik_custom/views/product_product.xml b/indoteknik_custom/views/product_product.xml
index b214dc87..1d04e708 100644
--- a/indoteknik_custom/views/product_product.xml
+++ b/indoteknik_custom/views/product_product.xml
@@ -15,6 +15,7 @@
<field name="qty_onhand_bandengan" optional="hide"/>
<field name="qty_incoming_bandengan" optional="hide"/>
<field name="qty_outgoing_bandengan" optional="hide"/>
+ <field name="qty_outgoing_mo_bandengan" optional="hide"/>
<field name="qty_available_bandengan" optional="hide"/>
<field name="qty_free_bandengan" optional="hide"/>
<field name="qty_rpo" optional="hide"/>
diff --git a/indoteknik_custom/views/project_views.xml b/indoteknik_custom/views/project_views.xml
new file mode 100644
index 00000000..3023fa18
--- /dev/null
+++ b/indoteknik_custom/views/project_views.xml
@@ -0,0 +1,13 @@
+<odoo>
+ <record id="view_task_kanban_inherit" model="ir.ui.view">
+ <field name="name">project.task.kanban.inherit</field>
+ <field name="model">project.task</field>
+ <field name="inherit_id" ref="project.view_task_kanban"/>
+ <field name="arch" type="xml">
+ <!-- Target field user_id di bagian kanban_bottom_right -->
+ <xpath expr="//div[@class='oe_kanban_bottom_right']/field[@name='user_id']" position="attributes">
+ <attribute name="widget" remove="many2one_avatar_user" />
+ </xpath>
+ </field>
+ </record>
+</odoo> \ No newline at end of file
diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml
index 920268bc..0fbbb5e7 100755
--- a/indoteknik_custom/views/purchase_order.xml
+++ b/indoteknik_custom/views/purchase_order.xml
@@ -36,7 +36,6 @@
</button>
<button name="button_unlock" position="after">
<button name="create_bill_pelunasan" string="Create Bill Pelunasan" type="object" class="oe_highlight" attrs="{'invisible': [('state', 'not in', ('purchase', 'done')), ('bills_pelunasan_id', '!=', False)]}"/>
-
</button>
<field name="date_order" position="before">
<field name="sale_order_id" attrs="{'readonly': [('state', 'not in', ['draft'])]}"/>
@@ -308,6 +307,7 @@
<field name="margin_item" optional="hide"/>
<field name="delivery_amt" optional="hide"/>
<field name="margin_deduct" optional="hide"/>
+ <field name="hold_outgoing_so" optional="hide"/>
<field name="margin_so"/>
</tree>
</field>
diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml
index 10c60e24..a599a7b8 100755
--- a/indoteknik_custom/views/sale_order.xml
+++ b/indoteknik_custom/views/sale_order.xml
@@ -4,211 +4,227 @@
<record id="sale_order_form_view_inherit" model="ir.ui.view">
<field name="name">Sale Order</field>
<field name="model">sale.order</field>
- <field name="inherit_id" ref="sale.view_order_form" />
+ <field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<button id="action_confirm" position="after">
<button name="calculate_line_no"
- string="Create No"
- type="object"
+ string="Create No"
+ type="object"
/>
<button name="sale_order_approve"
- string="Ask Approval"
- type="object"
- attrs="{'invisible': [('approval_status', '=', ['approved'])]}"
+ string="Ask Approval"
+ type="object"
+ attrs="{'invisible': [('approval_status', '=', ['approved'])]}"
+ />
+ <button name="hold_unhold_qty_outgoing_so"
+ string="Hold/Unhold Outgoing"
+ type="object"
+ attrs="{'invisible': [('state', 'in', ['cancel'])]}"
+ />
+ <button name="ask_retur_cancel_purchasing"
+ string="Ask Cancel Purchasing"
+ type="object"
+ attrs="{'invisible': [('state', 'in', ['cancel'])]}"
/>
<button name="action_web_approve"
- string="Web Approve"
- type="object"
- attrs="{'invisible': ['|', '|', ('create_uid', '!=', 25), ('web_approval', '!=', False), ('state', '!=', 'draft')]}"
+ string="Web Approve"
+ type="object"
+ attrs="{'invisible': ['|', '|', ('create_uid', '!=', 25), ('web_approval', '!=', False), ('state', '!=', 'draft')]}"
/>
<button name="indoteknik_custom.action_view_uangmuka_penjualan"
- string="UangMuka"
- type="action" attrs="{'invisible': [('approval_status', '!=', 'approved')]}" />
+ string="UangMuka"
+ type="action" attrs="{'invisible': [('approval_status', '!=', 'approved')]}"/>
</button>
<field name="payment_term_id" position="after">
- <field name="create_uid" invisible="1" />
- <field name="create_date" invisible="1" />
+ <field name="create_uid" invisible="1"/>
+ <field name="create_date" invisible="1"/>
<field name="shipping_cost_covered"
- attrs="{'required': ['|', ('create_date', '&gt;', '2023-06-15'), ('create_date', '=', False)]}" />
+ attrs="{'required': ['|', ('create_date', '&gt;', '2023-06-15'), ('create_date', '=', False)]}"/>
<field name="shipping_paid_by"
- attrs="{'required': ['|', ('create_date', '&gt;', '2023-06-15'), ('create_date', '=', False)]}" />
- <field name="delivery_amt" />
- <field name="ongkir_ke_xpdc" />
- <field name="metode_kirim_ke_xpdc" />
- <field name="fee_third_party" />
- <field name="biaya_lain_lain" />
- <field name="total_percent_margin" />
- <field name="total_margin_excl_third_party" readonly="1" />
- <field name="type_promotion" />
- <label for="voucher_id" />
+ attrs="{'required': ['|', ('create_date', '&gt;', '2023-06-15'), ('create_date', '=', False)]}"/>
+ <field name="delivery_amt"/>
+ <field name="ongkir_ke_xpdc"/>
+ <field name="metode_kirim_ke_xpdc"/>
+ <field name="fee_third_party"/>
+ <field name="biaya_lain_lain"/>
+ <field name="total_percent_margin"/>
+ <field name="total_margin_excl_third_party" readonly="1"/>
+ <field name="type_promotion"/>
+ <label for="voucher_id"/>
<div class="o_row">
<field name="voucher_id" id="voucher_id"
- attrs="{'readonly': ['|', ('state', 'not in', ['draft', 'sent']), ('applied_voucher_id', '!=', False)]}" />
- <field name="applied_voucher_id" invisible="1" />
+ attrs="{'readonly': ['|', ('state', 'not in', ['draft', 'sent']), ('applied_voucher_id', '!=', False)]}"/>
+ <field name="applied_voucher_id" invisible="1"/>
<button name="action_apply_voucher" type="object" string="Apply"
- confirm="Anda yakin untuk menggunakan voucher?"
- help="Apply the selected voucher" class="btn-link mb-1 px-0"
- icon="fa-plus"
- attrs="{'invisible': ['|', '|', ('voucher_id', '=', False), ('state', 'not in', ['draft', 'sent']), ('applied_voucher_id', '!=', False)]}"
+ confirm="Anda yakin untuk menggunakan voucher?"
+ help="Apply the selected voucher" class="btn-link mb-1 px-0"
+ icon="fa-plus"
+ attrs="{'invisible': ['|', '|', ('voucher_id', '=', False), ('state', 'not in', ['draft', 'sent']), ('applied_voucher_id', '!=', False)]}"
/>
<button name="cancel_voucher" type="object" string="Cancel"
- confirm="Anda yakin untuk membatalkan penggunaan voucher?"
- help="Cancel applied voucher" class="btn-link mb-1 px-0" icon="fa-times"
- attrs="{'invisible': ['|', ('applied_voucher_id', '=', False), ('state', 'not in', ['draft','sent'])]}"
+ confirm="Anda yakin untuk membatalkan penggunaan voucher?"
+ help="Cancel applied voucher" class="btn-link mb-1 px-0" icon="fa-times"
+ attrs="{'invisible': ['|', ('applied_voucher_id', '=', False), ('state', 'not in', ['draft','sent'])]}"
/>
</div>
- <label for="voucher_shipping_id" />
+ <label for="voucher_shipping_id"/>
<div class="o_row">
<field name="voucher_shipping_id" id="voucher_shipping_id"
- attrs="{'readonly': ['|', ('state', 'not in', ['draft', 'sent']), ('applied_voucher_shipping_id', '!=', False)]}" />
- <field name="applied_voucher_shipping_id" invisible="1" />
+ attrs="{'readonly': ['|', ('state', 'not in', ['draft', 'sent']), ('applied_voucher_shipping_id', '!=', False)]}"/>
+ <field name="applied_voucher_shipping_id" invisible="1"/>
<button name="action_apply_voucher_shipping" type="object" string="Apply"
- confirm="Anda yakin untuk menggunakan voucher?"
- help="Apply the selected voucher" class="btn-link mb-1 px-0"
- icon="fa-plus"
- attrs="{'invisible': ['|', '|', ('voucher_id', '=', False), ('state', 'not in', ['draft', 'sent']), ('applied_voucher_shipping_id', '!=', False)]}"
+ confirm="Anda yakin untuk menggunakan voucher?"
+ help="Apply the selected voucher" class="btn-link mb-1 px-0"
+ icon="fa-plus"
+ attrs="{'invisible': ['|', '|', ('voucher_id', '=', False), ('state', 'not in', ['draft', 'sent']), ('applied_voucher_shipping_id', '!=', False)]}"
/>
<button name="cancel_voucher_shipping" type="object" string="Cancel"
- confirm="Anda yakin untuk membatalkan penggunaan voucher?"
- help="Cancel applied voucher" class="btn-link mb-1 px-0" icon="fa-times"
- attrs="{'invisible': ['|', ('applied_voucher_shipping_id', '=', False), ('state', 'not in', ['draft','sent'])]}"
+ confirm="Anda yakin untuk membatalkan penggunaan voucher?"
+ help="Cancel applied voucher" class="btn-link mb-1 px-0" icon="fa-times"
+ attrs="{'invisible': ['|', ('applied_voucher_shipping_id', '=', False), ('state', 'not in', ['draft','sent'])]}"
/>
</div>
<button name="calculate_selling_price"
- string="Calculate Selling Price"
- type="object"
+ string="Calculate Selling Price"
+ type="object"
/>
</field>
+ <field name="approval_status" position="after">
+ <field name="notes"/>
+ </field>
<field name="source_id" position="attributes">
<attribute name="invisible">1</attribute>
</field>
<field name="user_id" position="after">
+ <field name="hold_outgoing" readonly="1" />
+ <field name="date_hold" readonly="1" widget="datetime" />
+ <field name="date_unhold" readonly="1" widget="datetime" />
<field name="helper_by_id" readonly="1" />
<field name="compute_fullfillment" invisible="1" />
</field>
<field name="tag_ids" position="after">
- <field name="eta_date_start" />
- <t t-esc="' to '" />
- <field name="eta_date" readonly="1" />
- <field name="expected_ready_to_ship" />
- <field name="flash_sale" />
- <field name="margin_after_delivery_purchase" />
- <field name="percent_margin_after_delivery_purchase" />
- <field name="total_weight" />
- <field name="pareto_status" />
+ <field name="eta_date_start"/>
+ <t t-esc="' to '"/>
+ <field name="eta_date" readonly="1"/>
+ <field name="expected_ready_to_ship"/>
+ <field name="flash_sale"/>
+ <field name="margin_after_delivery_purchase"/>
+ <field name="percent_margin_after_delivery_purchase"/>
+ <field name="total_weight"/>
+ <field name="pareto_status"/>
</field>
<field name="analytic_account_id" position="after">
- <field name="customer_type" required="1" />
- <field name="npwp" placeholder='99.999.999.9-999.999' required="1" />
- <field name="sppkp" attrs="{'required': [('customer_type', '=', 'pkp')]}" />
- <field name="email" required="1" />
- <field name="unreserve_id" />
- <field name="due_id" readonly="1" />
- <field name="vendor_approval_id" readonly="1" widget="many2many_tags" />
- <field name="source_id" domain="[('id', 'in', [32, 59, 60, 61])]" required="1" />
+ <field name="customer_type" readonly="1"/>
+ <field name="npwp" placeholder='99.999.999.9-999.999' readonly="1"/>
+ <field name="sppkp" attrs="{'required': [('customer_type', '=', 'pkp')]}" readonly="1"/>
+ <field name="email" required="1"/>
+ <field name="unreserve_id"/>
+ <field name="due_id" readonly="1"/>
+ <field name="vendor_approval_id" readonly="1" widget="many2many_tags"/>
+ <field name="source_id" domain="[('id', 'in', [32, 59, 60, 61])]" required="1"/>
<button name="override_allow_create_invoice"
- string="Override Create Invoice"
- type="object"
+ string="Override Create Invoice"
+ type="object"
/>
- <button string="Estimate Shipping" type="object" name="action_estimate_shipping" />
+ <button string="Estimate Shipping" type="object" name="action_estimate_shipping"/>
</field>
<field name="partner_shipping_id" position="after">
- <field name="real_shipping_id" />
- <field name="real_invoice_id" />
- <field name="approval_status" />
+ <field name="real_shipping_id"/>
+ <field name="real_invoice_id"/>
+ <field name="approval_status"/>
<field name="sales_tax_id"
- domain="[('type_tax_use','=','sale'), ('active', '=', True)]" required="1" />
- <field name="carrier_id" required="1" />
- <field name="delivery_service_type" readonly="1" />
- <field name="shipping_option_id" />
+ domain="[('type_tax_use','=','sale'), ('active', '=', True)]" required="1"/>
+ <field name="carrier_id" required="1"/>
+ <field name="delivery_service_type" readonly="1"/>
+ <field name="shipping_option_id"/>
</field>
<field name="medium_id" position="after">
- <field name="date_doc_kirim" readonly="1" />
- <field name="notification" readonly="1" />
+ <field name="date_doc_kirim" readonly="1"/>
+ <field name="notification" readonly="1"/>
</field>
<xpath expr="//form/sheet/notebook/page/field[@name='order_line']"
- position="attributes">
+ position="attributes">
<attribute name="attrs">
- {'readonly': [('state', 'in', ('done','cancel'))]}
+ {'readonly': [('state', 'in', ('done', 'cancel'))]}
</attribute>
</xpath>
<xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree"
- position="inside">
- <field name="desc_updatable" invisible="1" />
+ position="inside">
+ <field name="desc_updatable" invisible="1"/>
</xpath>
<xpath
- expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='name']"
- position="attributes">
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='name']"
+ position="attributes">
<attribute name="modifiers">
{'readonly': [('desc_updatable', '=', False)]}
</attribute>
</xpath>
<xpath
- expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_unit']"
- position="attributes">
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_unit']"
+ position="attributes">
<attribute name="attrs">
{
- 'readonly': [
- '|',
- ('qty_invoiced', '>', 0),
- ('parent.approval_status', '!=', False)
- ]
+ 'readonly': [
+ '|',
+ ('qty_invoiced', '>', 0),
+ ('parent.approval_status', '!=', False)
+ ]
}
</attribute>
</xpath>
<div name="invoice_lines" position="before">
<div name="vendor_id" groups="base.group_no_one"
- attrs="{'invisible': [('display_type', '!=', False)]}">
- <label for="vendor_id" />
+ attrs="{'invisible': [('display_type', '!=', False)]}">
+ <label for="vendor_id"/>
<div name="vendor_id">
<field name="vendor_id"
- attrs="{'readonly': [('parent.approval_status', '=', 'approved')]}"
- domain="[('parent_id', '=', False)]"
- options="{'no_create': True}" class="oe_inline" />
+ attrs="{'readonly': [('parent.approval_status', '=', 'approved')]}"
+ domain="[('parent_id', '=', False)]"
+ options="{'no_create': True}" class="oe_inline"/>
</div>
</div>
</div>
<div name="invoice_lines" position="before">
<div name="purchase_price" groups="base.group_no_one"
- attrs="{'invisible': [('display_type', '!=', False)]}">
- <label for="purchase_price" />
- <field name="purchase_price" />
+ attrs="{'invisible': [('display_type', '!=', False)]}">
+ <label for="purchase_price"/>
+ <field name="purchase_price"/>
</div>
</div>
<div name="invoice_lines" position="before">
<div name="purchase_tax_id" groups="base.group_no_one"
- attrs="{'invisible': [('display_type', '!=', False)]}">
- <label for="purchase_tax_id" />
+ attrs="{'invisible': [('display_type', '!=', False)]}">
+ <label for="purchase_tax_id"/>
<div name="purchase_tax_id">
- <field name="purchase_tax_id" />
+ <field name="purchase_tax_id"/>
</div>
</div>
</div>
<div name="invoice_lines" position="before">
<div name="item_percent_margin" groups="base.group_no_one"
- attrs="{'invisible': [('display_type', '!=', False)]}">
- <label for="item_percent_margin" />
- <field name="item_percent_margin" />
+ attrs="{'invisible': [('display_type', '!=', False)]}">
+ <label for="item_percent_margin"/>
+ <field name="item_percent_margin"/>
</div>
</div>
<div name="invoice_lines" position="before">
<div name="price_subtotal" groups="base.group_no_one"
- attrs="{'invisible': [('display_type', '!=', False)]}">
- <label for="price_subtotal" />
- <field name="price_subtotal" />
+ attrs="{'invisible': [('display_type', '!=', False)]}">
+ <label for="price_subtotal"/>
+ <field name="price_subtotal"/>
</div>
</div>
<xpath
- expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_total']"
- position="after">
- <field name="qty_free_bu" optional="hide" />
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_total']"
+ position="after">
+ <field name="qty_free_bu" optional="hide"/>
<field name="vendor_id"
- attrs="{'readonly': [('parent.approval_status', '=', 'approved')], 'invisible': [('display_type', '!=', False)]}"
- domain="[('parent_id', '=', False)]" options="{'no_create':True}" />
- <field name="vendor_md_id" optional="hide" />
+ attrs="{'readonly': [('parent.approval_status', '=', 'approved')], 'invisible': [('display_type', '!=', False)]}"
+ domain="[('parent_id', '=', False)]" options="{'no_create':True}"/>
+ <field name="vendor_md_id" optional="hide"/>
<field name="purchase_price"
- attrs="
+ attrs="
{
'readonly': [
'|',
@@ -216,35 +232,35 @@
('parent.approval_status', '!=', False)
]
}
- " />
- <field name="purchase_price_md" optional="hide" />
+ "/>
+ <field name="purchase_price_md" optional="hide"/>
<field name="purchase_tax_id"
- attrs="{'readonly': [('parent.approval_status', '!=', False)]}"
- domain="[('type_tax_use','=','purchase')]" options="{'no_create':True}" />
- <field name="item_percent_margin" />
- <field name="item_margin" optional="hide" />
- <field name="margin_md" optional="hide" />
- <field name="note" optional="hide" />
- <field name="note_procurement" optional="hide" />
- <field name="vendor_subtotal" optional="hide" />
- <field name="weight" optional="hide" />
- <field name="amount_voucher_disc" string="Voucher" readonly="1" optional="hide" />
- <field name="order_promotion_id" string="Promotion" readonly="1" optional="hide" />
+ attrs="{'readonly': [('parent.approval_status', '!=', False)]}"
+ domain="[('type_tax_use','=','purchase')]" options="{'no_create':True}"/>
+ <field name="item_percent_margin"/>
+ <field name="item_margin" optional="hide"/>
+ <field name="margin_md" optional="hide"/>
+ <field name="note" optional="hide"/>
+ <field name="note_procurement" optional="hide"/>
+ <field name="vendor_subtotal" optional="hide"/>
+ <field name="weight" optional="hide"/>
+ <field name="amount_voucher_disc" string="Voucher" readonly="1" optional="hide"/>
+ <field name="order_promotion_id" string="Promotion" readonly="1" optional="hide"/>
</xpath>
<xpath
- expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='product_id']"
- position="before">
- <field name="line_no" readonly="1" optional="hide" />
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='product_id']"
+ position="before">
+ <field name="line_no" readonly="1" optional="hide"/>
</xpath>
<xpath
- expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='qty_delivered']"
- position="before">
- <field name="qty_reserved" invisible="1" />
- <field name="reserved_from" readonly="1" optional="hide" />
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='qty_delivered']"
+ position="before">
+ <field name="qty_reserved" invisible="1"/>
+ <field name="reserved_from" readonly="1" optional="hide"/>
</xpath>
<xpath
- expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='product_id']"
- position="attributes">
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='product_id']"
+ position="attributes">
<attribute name="options">{'no_create': True}</attribute>
</xpath>
<!-- <xpath
@@ -253,35 +269,42 @@
<attribute name="required">1</attribute>
</xpath> -->
<field name="amount_total" position="after">
- <field name="grand_total" />
- <label for="amount_voucher_disc" string="Voucher" />
+ <field name="grand_total"/>
+ <label for="amount_voucher_disc" string="Voucher"/>
<div>
- <field class="mb-0" name="amount_voucher_disc" string="Voucher" readonly="1" />
+ <field class="mb-0" name="amount_voucher_disc" string="Voucher" readonly="1"/>
<div class="text-right mb-2">
<small>*Hanya informasi</small>
</div>
</div>
- <label for="amount_voucher_shipping_disc" string="Voucher Shipping" />
+ <label for="amount_voucher_shipping_disc" string="Voucher Shipping"/>
<div>
<field class="mb-0" name="amount_voucher_shipping_disc"
- string="Voucher Shipping" readonly="1" />
+ string="Voucher Shipping" readonly="1"/>
<div class="text-right mb-2">
<small>*Hanya informasi</small>
</div>
</div>
- <field name="total_margin" />
- <field name="total_percent_margin" />
+ <field name="total_margin"/>
+ <field name="total_percent_margin"/>
+ <field name="total_before_margin"/>
</field>
<field name="effective_date" position="after">
- <field name="carrier_id" />
- <field name="estimated_arrival_days" />
- <field name="picking_iu_id" />
- <field name="note_ekspedisi" />
+ <field name="carrier_id"/>
+ <field name="estimated_arrival_days"/>
+ <field name="picking_iu_id"/>
+ <field name="note_ekspedisi"/>
</field>
<field name="carrier_id" position="attributes">
<attribute name="attrs">
{'readonly': [('approval_status', '=', 'approved'), ('state', 'not in',
- ['cancel','draft'])]}
+ ['cancel', 'draft'])]}
+ </attribute>
+ </field>
+ <field name="payment_term_id" position="attributes">
+ <attribute name="attrs">
+ {'readonly': [('approval_status', '=', 'approved'), ('state', 'not in',
+ ['cancel', 'draft'])]}
</attribute>
</field>
@@ -289,55 +312,55 @@
<page string="Website" name="customer_purchase_order">
<group>
<group>
- <field name="partner_purchase_order_name" readonly="True" />
- <field name="partner_purchase_order_description" readonly="True" />
- <field name="partner_purchase_order_file" readonly="True" />
- <field name="note_website" readonly="True" />
- <field name="web_approval" readonly="True" />
+ <field name="partner_purchase_order_name" readonly="True"/>
+ <field name="partner_purchase_order_description" readonly="True"/>
+ <field name="partner_purchase_order_file" readonly="True"/>
+ <field name="note_website" readonly="True"/>
+ <field name="web_approval" readonly="True"/>
</group>
<group>
<button name="generate_payment_link_midtrans_sales_order"
- string="Create Payment Link"
- type="object"
+ string="Create Payment Link"
+ type="object"
/>
- <field name="payment_link_midtrans" readonly="True" widget="url" />
- <field name="gross_amount" readonly="True" />
- <field name="payment_type" readonly="True" />
- <field name="payment_status" readonly="True" />
- <field name="payment_qr_code" widget="image" readonly="True" />
+ <field name="payment_link_midtrans" readonly="True" widget="url"/>
+ <field name="gross_amount" readonly="True"/>
+ <field name="payment_type" readonly="True"/>
+ <field name="payment_status" readonly="True"/>
+ <field name="payment_qr_code" widget="image" readonly="True"/>
</group>
</group>
</page>
<page string="Promotion" name="page_promotion">
<field name="order_promotion_ids" readonly="1">
<tree options="{'no_open': True}">
- <field name="program_line_id" />
- <field name="quantity" />
- <field name="is_applied" />
+ <field name="program_line_id"/>
+ <field name="quantity"/>
+ <field name="is_applied"/>
</tree>
<form>
<group>
- <field name="program_line_id" />
- <field name="quantity" />
- <field name="is_applied" />
+ <field name="program_line_id"/>
+ <field name="quantity"/>
+ <field name="is_applied"/>
</group>
</form>
</field>
</page>
<page string="Matches PO" name="page_matches_po" invisible="1">
- <field name="order_sales_match_line" readonly="1" />
+ <field name="order_sales_match_line" readonly="1"/>
</page>
<!-- <page string="Fullfillment" name="page_sale_order_fullfillment">
<field name="fullfillment_line" readonly="1"/>
</page> -->
<page string="Fulfillment v2" name="page_sale_order_fullfillment2">
- <field name="fulfillment_line_v2" readonly="1" />
+ <field name="fulfillment_line_v2" readonly="1"/>
</page>
<page string="Reject Line" name="page_sale_order_reject_line">
- <field name="reject_line" readonly="0" />
+ <field name="reject_line" readonly="0"/>
</page>
<page string="Koli" name="page_sales_order_koli_line">
- <field name="koli_lines" readonly="1" />
+ <field name="koli_lines" readonly="1"/>
</page>
</page>
</field>
@@ -349,15 +372,15 @@
<field name="arch" type="xml">
<form string="Cancel Reason">
<group>
- <field name="reason_cancel" widget="selection" />
- <field name="attachment_bukti" widget="many2many_binary" required="1" />
+ <field name="reason_cancel" widget="selection"/>
+ <field name="attachment_bukti" widget="many2many_binary" required="1"/>
<field name="nomor_so_pengganti"
- attrs="{'invisible': [('reason_cancel', '!=', 'ganti_quotation')]}" />
+ attrs="{'invisible': [('reason_cancel', '!=', 'ganti_quotation')]}"/>
</group>
<footer>
<button string="Confirm" type="object" name="confirm_reject"
- class="btn-primary" />
- <button string="Cancel" class="btn-secondary" special="cancel" />
+ class="btn-primary"/>
+ <button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
@@ -374,42 +397,44 @@
<record id="sale_order_tree_view_inherit" model="ir.ui.view">
<field name="name">Sale Order</field>
<field name="model">sale.order</field>
- <field name="inherit_id" ref="sale.view_quotation_tree_with_onboarding" />
+ <field name="inherit_id" ref="sale.view_quotation_tree_with_onboarding"/>
<field name="arch" type="xml">
<field name="state" position="after">
- <field name="approval_status" />
- <field name="client_order_ref" />
- <field name="payment_type" optional="hide" />
- <field name="payment_status" optional="hide" />
- <field name="pareto_status" optional="hide" />
- <field name="shipping_method_picking" optional="hide" />
+ <field name="approval_status"/>
+ <field name="client_order_ref"/>
+ <field name="notes"/>
+ <field name="payment_type" optional="hide"/>
+ <field name="payment_status" optional="hide"/>
+ <field name="pareto_status" optional="hide"/>
+ <field name="shipping_method_picking" optional="hide"/>
+ <field name="hold_outgoing" optional="hide"/>
</field>
</field>
</record>
<record id="sales_order_tree_view_inherit" model="ir.ui.view">
<field name="name">Sale Order</field>
<field name="model">sale.order</field>
- <field name="inherit_id" ref="sale.view_order_tree" />
+ <field name="inherit_id" ref="sale.view_order_tree"/>
<field name="arch" type="xml">
<field name="state" position="after">
- <field name="approval_status" />
- <field name="client_order_ref" />
- <field name="so_status" />
- <field name="date_status_done" />
- <field name="date_kirim_ril" />
- <field name="date_driver_departure" />
- <field name="date_driver_arrival" />
- <field name="payment_type" optional="hide" />
- <field name="payment_status" optional="hide" />
- <field name="pareto_status" optional="hide" />
+ <field name="approval_status"/>
+ <field name="client_order_ref"/>
+ <field name="so_status"/>
+ <field name="date_status_done"/>
+ <field name="date_kirim_ril"/>
+ <field name="date_driver_departure"/>
+ <field name="date_driver_arrival"/>
+ <field name="payment_type" optional="hide"/>
+ <field name="payment_status" optional="hide"/>
+ <field name="pareto_status" optional="hide"/>
</field>
</field>
</record>
<record id="sale_order_multi_update_ir_actions_server" model="ir.actions.server">
<field name="name">Mark As Cancel</field>
- <field name="model_id" ref="sale.model_sale_order" />
- <field name="binding_model_id" ref="sale.model_sale_order" />
+ <field name="model_id" ref="sale.model_sale_order"/>
+ <field name="binding_model_id" ref="sale.model_sale_order"/>
<field name="binding_view_types">form,list</field>
<field name="state">code</field>
<field name="code">action = records.open_form_multi_update_state()</field>
@@ -417,32 +442,32 @@
<record id="sale_order_update_multi_actions_server" model="ir.actions.server">
<field name="name">Mark As Completed</field>
- <field name="model_id" ref="sale.model_sale_order" />
- <field name="binding_model_id" ref="sale.model_sale_order" />
+ <field name="model_id" ref="sale.model_sale_order"/>
+ <field name="binding_model_id" ref="sale.model_sale_order"/>
<field name="state">code</field>
<field name="code">action = records.open_form_multi_update_status()</field>
</record>
<record id="mail_template_sale_order_web_approve_notification" model="mail.template">
<field name="name">Sale Order: Web Approve Notification</field>
- <field name="model_id" ref="indoteknik_custom.model_sale_order" />
+ <field name="model_id" ref="indoteknik_custom.model_sale_order"/>
<field name="subject">Permintaan Persetujuan Pesanan ${object.name} di Indoteknik.com</field>
<field name="email_from">sales@indoteknik.com</field>
<field name="email_to">${object.partner_id.email | safe}</field>
<field name="email_cc">${object.partner_id.get_approve_partner_ids("email_comma_sep")}</field>
<field name="body_html" type="html">
<table border="0" cellpadding="0" cellspacing="0"
- style="padding: 16px 0; background-color: #F1F1F1; font-family:Inter, Helvetica, Verdana, Arial,sans-serif; line-height: 24px; color: #454748; width: 100%; border-collapse:separate;">
+ style="padding: 16px 0; background-color: #F1F1F1; font-family:Inter, Helvetica, Verdana, Arial,sans-serif; line-height: 24px; color: #454748; width: 100%; border-collapse:separate;">
<tr>
<td align="center">
<table border="0" cellpadding="0" cellspacing="0" width="590"
- style="font-size: 13px; padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
+ style="font-size: 13px; padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
<tbody>
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0"
- width="590"
- style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
+ width="590"
+ style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
<tr>
<td style="padding-bottom: 24px;">
Dear
@@ -459,8 +484,8 @@
<tr>
<td style="padding-bottom: 16px;">
<a
- href="https://indoteknik.com/my/quotations/${object.id}"
- style="color: white; background-color: #C53030; border: none; border-radius: 6px; padding: 4px 8px; width: fit-content; display: block;">
+ href="https://indoteknik.com/my/quotations/${object.id}"
+ style="color: white; background-color: #C53030; border: none; border-radius: 6px; padding: 4px 8px; width: fit-content; display: block;">
Lihat Pesanan
</a>
</td>
@@ -503,11 +528,11 @@
<field name="model">sales.order.purchase.match</field>
<field name="arch" type="xml">
<tree editable="top" create="false" delete="false">
- <field name="purchase_order_id" readonly="1" />
- <field name="purchase_line_id" readonly="1" />
- <field name="product_id" readonly="1" />
- <field name="qty_so" readonly="1" />
- <field name="qty_po" readonly="1" />
+ <field name="purchase_order_id" readonly="1"/>
+ <field name="purchase_line_id" readonly="1"/>
+ <field name="product_id" readonly="1"/>
+ <field name="qty_so" readonly="1"/>
+ <field name="qty_po" readonly="1"/>
</tree>
</field>
</record>
@@ -519,9 +544,9 @@
<field name="model">sales.order.koli</field>
<field name="arch" type="xml">
<tree editable="top" create="false" delete="false">
- <field name="koli_id" readonly="1" />
- <field name="picking_id" readonly="1" />
- <field name="state" readonly="1" />
+ <field name="koli_id" readonly="1"/>
+ <field name="picking_id" readonly="1"/>
+ <field name="state" readonly="1"/>
</tree>
</field>
</record>
@@ -535,14 +560,14 @@
<field name="model">sales.order.fulfillment.v2</field>
<field name="arch" type="xml">
<tree editable="top" create="false">
- <field name="product_id" readonly="1" />
- <field name="so_qty" readonly="1" optional="show" />
- <field name="reserved_stock_qty" readonly="1" optional="show" />
- <field name="delivered_qty" readonly="1" optional="hide" />
- <field name="po_ids" widget="many2many_tags" readonly="1" optional="show" />
- <field name="po_qty" readonly="1" optional="show" />
- <field name="received_qty" readonly="1" optional="show" />
- <field name="purchaser" readonly="1" optional="hide" />
+ <field name="product_id" readonly="1"/>
+ <field name="so_qty" readonly="1" optional="show"/>
+ <field name="reserved_stock_qty" readonly="1" optional="show"/>
+ <field name="delivered_qty" readonly="1" optional="hide"/>
+ <field name="po_ids" widget="many2many_tags" readonly="1" optional="show"/>
+ <field name="po_qty" readonly="1" optional="show"/>
+ <field name="received_qty" readonly="1" optional="show"/>
+ <field name="purchaser" readonly="1" optional="hide"/>
</tree>
</field>
</record>
@@ -552,10 +577,10 @@
<field name="model">sales.order.fullfillment</field>
<field name="arch" type="xml">
<tree editable="top" create="false">
- <field name="product_id" readonly="1" />
- <field name="reserved_from" readonly="1" />
- <field name="qty_fullfillment" readonly="1" />
- <field name="user_id" readonly="1" />
+ <field name="product_id" readonly="1"/>
+ <field name="reserved_from" readonly="1"/>
+ <field name="qty_fullfillment" readonly="1"/>
+ <field name="user_id" readonly="1"/>
</tree>
</field>
</record>
@@ -567,9 +592,9 @@
<field name="model">sales.order.reject</field>
<field name="arch" type="xml">
<tree editable="top" create="false">
- <field name="product_id" readonly="1" />
- <field name="qty_reject" readonly="1" />
- <field name="reason_reject" readonly="0" />
+ <field name="product_id" readonly="1"/>
+ <field name="qty_reject" readonly="1"/>
+ <field name="reason_reject" readonly="0"/>
</tree>
</field>
</record>
@@ -578,8 +603,8 @@
<data>
<record id="sale_order_multi_create_uangmuka_ir_actions_server" model="ir.actions.server">
<field name="name">Uang Muka</field>
- <field name="model_id" ref="sale.model_sale_order" />
- <field name="binding_model_id" ref="sale.model_sale_order" />
+ <field name="model_id" ref="sale.model_sale_order"/>
+ <field name="binding_model_id" ref="sale.model_sale_order"/>
<field name="state">code</field>
<field name="code">action = records.open_form_multi_create_uang_muka()</field>
</record>
@@ -588,24 +613,24 @@
<data>
<record id="mail_template_sale_order_notification_to_salesperson" model="mail.template">
<field name="name">Sale Order: Notification to Salesperson</field>
- <field name="model_id" ref="sale.model_sale_order" />
+ <field name="model_id" ref="sale.model_sale_order"/>
<field name="subject">Konsolidasi Pengiriman</field>
<field name="email_from">sales@indoteknik.com</field>
<field name="email_to">${object.user_id.login | safe}</field>
<field name="body_html" type="html">
<table border="0" cellpadding="0" cellspacing="0"
- style="padding-top: 16px; background-color: #F1F1F1; font-family:Inter, Helvetica, Verdana, Arial,sans-serif; line-height: 24px; color: #454748; width: 100%; border-collapse:separate;">
+ style="padding-top: 16px; background-color: #F1F1F1; font-family:Inter, Helvetica, Verdana, Arial,sans-serif; line-height: 24px; color: #454748; width: 100%; border-collapse:separate;">
<tr>
<td align="center">
<table border="0" cellpadding="0" cellspacing="0" width="590"
- style="font-size: 13px; padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
+ style="font-size: 13px; padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
<!-- HEADER -->
<tbody>
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0"
- width="590"
- style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
+ width="590"
+ style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
<tr>
<td valign="middle">
<span></span>
@@ -618,8 +643,8 @@
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0"
- width="590"
- style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
+ width="590"
+ style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
<tr>
<td style="padding-bottom: 24px;">Dear
${salesperson_name},</td>
@@ -636,7 +661,7 @@
<tr>
<td>
<table border="1" cellpadding="5"
- cellspacing="0">
+ cellspacing="0">
<thead>
<tr>
<th>Nama Pesanan</th>
@@ -655,7 +680,7 @@
<tr>
<td style="text-align:center;">
<hr width="100%"
- style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;" />
+ style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>
</td>
</tr>
</table>
diff --git a/indoteknik_custom/views/shipment_group.xml b/indoteknik_custom/views/shipment_group.xml
index e9eec41b..a4f82e27 100644
--- a/indoteknik_custom/views/shipment_group.xml
+++ b/indoteknik_custom/views/shipment_group.xml
@@ -7,6 +7,8 @@
<tree default_order="create_date desc">
<field name="number"/>
<field name="partner_id"/>
+ <field name="carrier_id"/>
+ <field name="total_colly_line"/>
</tree>
</field>
</record>
@@ -16,11 +18,10 @@
<field name="model">shipment.group.line</field>
<field name="arch" type="xml">
<tree editable="bottom">
- <field name="picking_id" required="1"/>
<field name="partner_id" readonly="1"/>
+ <field name="picking_id" required="1"/>
<field name="sale_id" readonly="1"/>
- <field name="shipping_paid_by" readonly="1"/>
- <field name="state" readonly="1"/>
+ <field name="total_colly" readonly="1"/>
</tree>
</field>
</record>
@@ -37,6 +38,8 @@
</group>
<group>
<field name="partner_id" readonly="1"/>
+ <field name="carrier_id" readonly="1"/>
+ <field name="total_colly_line" readonly="1"/>
</group>
</group>
<notebook>
diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml
index b45debd0..ae77ab9a 100644
--- a/indoteknik_custom/views/stock_picking.xml
+++ b/indoteknik_custom/views/stock_picking.xml
@@ -73,6 +73,11 @@
type="object"
attrs="{'invisible': [('carrier_id', '!=', 9)]}"
/>
+ <button name="action_get_kgx_pod"
+ string="Tracking KGX"
+ type="object"
+ attrs="{'invisible': [('carrier_id', '!=', 173)]}"
+ />
<button name="button_state_approve_md"
string="Approve MD Gudang Selisih"
type="object"
@@ -219,6 +224,13 @@
<field name="lalamove_image_url" invisible="1"/>
<field name="lalamove_image_html"/>
</group>
+ <group attrs="{'invisible': [('carrier_id', '!=', 173)]}">
+ <field name="kgx_pod_photo_url" invisible="1"/>
+ <field name="kgx_pod_photo"/>
+ <field name="kgx_pod_signature" invisible="1"/>
+ <field name="kgx_pod_receive_time"/>
+ <field name="kgx_pod_receiver"/>
+ </group>
</group>
</page>
<page string="Check Product" name="check_product" attrs="{'invisible': [('picking_type_code', '=', 'outgoing')]}">
diff --git a/indoteknik_custom/views/user_pengajuan_tempo_request.xml b/indoteknik_custom/views/user_pengajuan_tempo_request.xml
index 339ce8db..898d5b2a 100644
--- a/indoteknik_custom/views/user_pengajuan_tempo_request.xml
+++ b/indoteknik_custom/views/user_pengajuan_tempo_request.xml
@@ -426,7 +426,7 @@
<menuitem
id="menu_user_pengajuan_tempo_request"
name="User Pengajuan Tempo Request"
- parent="res_partner_menu_user"
+ parent="account.menu_finance_receivables"
sequence="3"
action="action_user_pengajuan_tempo_request"
/>
diff --git a/indoteknik_custom/views/voucher.xml b/indoteknik_custom/views/voucher.xml
index ae958f05..78e42969 100755
--- a/indoteknik_custom/views/voucher.xml
+++ b/indoteknik_custom/views/voucher.xml
@@ -27,63 +27,71 @@
<group>
<group>
<field name="image" widget="image" width="120"/>
- <field name="name" required="1" />
- <field name="code" required="1" />
- <field name="visibility" required="1" />
+ <field name="name" required="1"/>
+ <field name="code" required="1"/>
+ <field name="voucher_category" widget="many2many"/>
+ <field name="visibility" required="1"/>
<field name="start_time" required="1"/>
<field name="end_time" required="1"/>
<field name="limit" required="1"/>
<field name="limit_user" required="1"/>
- <field name="apply_type" required="1" />
- <field name="account_type" required="1" />
- <field name="show_on_email" />
- <field name="excl_pricelist_ids" widget="many2many_tags" domain="[('id', 'in', [4, 15037, 15038, 15039, 17023, 17024, 17025, 17026,17027])]"/>
+ <field name="apply_type" required="1"/>
+ <field name="account_type" required="1"/>
+ <field name="show_on_email"/>
+ <field name="excl_pricelist_ids" widget="many2many_tags"
+ domain="[('id', 'in', [4, 15037, 15038, 15039, 17023, 17024, 17025, 17026,17027])]"/>
</group>
- <group string="Discount Settings" attrs="{'invisible': [('apply_type', 'not in', ['all', 'shipping'])]}">
- <field name="min_purchase_amount" widget="monetary" required="1" />
- <field name="discount_type" attrs="{'invisible': [('apply_type','not in', ['all', 'shipping'])], 'required': [('apply_type', 'in', ['all', 'shipping'])]}" />
+ <group string="Discount Settings"
+ attrs="{'invisible': [('apply_type', 'not in', ['all', 'shipping'])]}">
+ <field name="min_purchase_amount" widget="monetary" required="1"/>
+ <field name="discount_type"
+ attrs="{'invisible': [('apply_type','not in', ['all', 'shipping'])], 'required': [('apply_type', 'in', ['all', 'shipping'])]}"/>
- <label for="max_discount_amount" string="Discount Amount" />
+ <label for="max_discount_amount" string="Discount Amount"/>
<div class="d-flex align-items-center">
- <span
- class="mr-1 font-weight-bold"
- attrs="{'invisible': [('discount_type', '!=', 'fixed_price')]}"
+ <span
+ class="mr-1 font-weight-bold"
+ attrs="{'invisible': [('discount_type', '!=', 'fixed_price')]}"
>
Rp
</span>
- <field class="mb-0" name="discount_amount" required="1" />
- <span
- class="ml-1 font-weight-bold"
- attrs="{'invisible': [('discount_type', '!=', 'percentage')]}"
+ <field class="mb-0" name="discount_amount" required="1"/>
+ <span
+ class="ml-1 font-weight-bold"
+ attrs="{'invisible': [('discount_type', '!=', 'percentage')]}"
>
%
</span>
</div>
- <field name="max_discount_amount" widget="monetary" required="1" attrs="{'invisible': [('discount_type', '!=', 'percentage')]}"/>
+ <field name="max_discount_amount" widget="monetary" required="1"
+ attrs="{'invisible': [('discount_type', '!=', 'percentage')]}"/>
</group>
</group>
<notebook>
- <page name="voucher_line" string="Voucher Line" attrs="{'invisible': [('apply_type', '!=', 'brand')]}">
+ <page name="voucher_line" string="Voucher Line"
+ attrs="{'invisible': [('apply_type', '!=', 'brand')]}">
<field name="voucher_line">
<tree editable="bottom">
- <field name="manufacture_id" required="1" />
- <field name="min_purchase_amount" required="1" />
- <field name="discount_type" required="1" />
- <field name="discount_amount" required="1" />
- <field name="max_discount_amount" required="1" attrs="{'readonly': [('discount_type', '!=', 'percentage')]}" />
+ <field name="manufacture_id" required="1"/>
+ <field name="min_purchase_amount" required="1"/>
+ <field name="discount_type" required="1"/>
+ <field name="discount_amount" required="1"/>
+ <field name="max_discount_amount" required="1"
+ attrs="{'readonly': [('discount_type', '!=', 'percentage')]}"/>
</tree>
</field>
</page>
<page name="description" string="Description">
- <label for="description" string="Max 120 characters:" class="font-weight-normal mb-2 oe_edit_only"/>
- <field name="description" placeholder="Insert short description..." />
+ <label for="description" string="Max 120 characters:"
+ class="font-weight-normal mb-2 oe_edit_only"/>
+ <field name="description" placeholder="Insert short description..."/>
</page>
<page name="terms_conditions" string="Terms and Conditions">
- <field name="terms_conditions" />
+ <field name="terms_conditions"/>
</page>
<page name="order_page" string="Orders">
- <field name="order_ids" readonly="1" />
+ <field name="order_ids" readonly="1"/>
</page>
</notebook>
</sheet>
@@ -92,10 +100,10 @@
</record>
<menuitem id="voucher"
- name="Voucher"
- parent="website_sale.menu_catalog"
- sequence="1"
- action="voucher_action"
+ name="Voucher"
+ parent="website_sale.menu_catalog"
+ sequence="1"
+ action="voucher_action"
/>
</data>
</odoo> \ No newline at end of file