diff options
| author | Indoteknik . <it@fixcomart.co.id> | 2025-05-26 11:25:15 +0700 |
|---|---|---|
| committer | Indoteknik . <it@fixcomart.co.id> | 2025-05-26 11:25:15 +0700 |
| commit | 7d7a6d4421a664e2ddd9308b15ea9f9e5e54c4a9 (patch) | |
| tree | fd8b1877af11acdbbb627f03ea5b7189281a009b | |
| parent | bab061bc003f132e738d7ad2f9d99df903392d1a (diff) | |
| parent | c1aefea6e72798848d090abb32bb753c550ce76b (diff) | |
(andri) resolved conflict
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', '>', '2023-06-15'), ('create_date', '=', False)]}" /> + attrs="{'required': ['|', ('create_date', '>', '2023-06-15'), ('create_date', '=', False)]}"/> <field name="shipping_paid_by" - attrs="{'required': ['|', ('create_date', '>', '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', '>', '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 |
