diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2025-01-13 17:26:30 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2025-01-13 17:26:30 +0700 |
| commit | 1c9308c1a18bd89612dc7fca5726cbf96c28deed (patch) | |
| tree | acc36cfa86d66b4e230aa5cf6830e2c6724d77a9 | |
| parent | 9f994de3f13f6be24d17233bf6890e6e88dd959b (diff) | |
| parent | 46e968a3b28c00aa74e6f09b451d5a87e8523043 (diff) | |
Merge branch 'odoo-production' into iman/pengajuan-tempo
# Conflicts:
# indoteknik_custom/security/ir.model.access.csv
18 files changed, 564 insertions, 40 deletions
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 07af1802..d44f85db 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -8,7 +8,7 @@ 'author': 'Rafi Zadanly', 'website': '', 'images': ['assets/favicon.ico'], - 'depends': ['base', 'coupon', 'delivery', 'sale', 'sale_management', 'vit_kelurahan'], + 'depends': ['base', 'coupon', 'delivery', 'sale', 'sale_management', 'vit_kelurahan', 'vit_efaktur' ], 'data': [ 'security/ir.model.access.csv', 'views/group_partner.xml', @@ -157,6 +157,7 @@ 'report/report_invoice.xml', 'report/report_picking.xml', 'report/report_sale_order.xml', + 'views/coretax_faktur.xml', ], 'demo': [], 'css': [], diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index c0b32e18..3990e81c 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -137,3 +137,5 @@ from . import user_pengajuan_tempo from . import approval_retur_picking from . import va_multi_approve from . import va_multi_reject +from . import stock_immediate_transfer +from . import coretax_fatur diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 725b3c2d..42678847 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -325,3 +325,35 @@ class AccountMove(models.Model): # if rec.statement_line_id and not rec.statement_line_id.statement_id.is_edit and rec.statement_line_id.statement_id.state == 'confirm': # raise UserError('Bank Statement di Lock, Minta admin reconcile untuk unlock') # return res + + def validate_faktur_for_export(self): + invoices = self.filtered(lambda inv: not inv.is_efaktur_exported and + inv.state == 'posted' and + inv.move_type == 'out_invoice') + + invalid_invoices = self - invoices + if invalid_invoices: + invalid_ids = ", ".join(str(inv.id) for inv in invalid_invoices) + raise UserError(_( + "Faktur dengan ID berikut tidak valid untuk diekspor: {}.\n" + "Pastikan faktur dalam status 'posted', belum diekspor, dan merupakan 'out_invoice'.".format(invalid_ids) + )) + + return invoices + + def export_faktur_to_xml(self): + + valid_invoices = self.validate_faktur_for_export() + + # Panggil model coretax.faktur untuk menghasilkan XML + coretax_faktur = self.env['coretax.faktur'].create({}) + response = coretax_faktur.export_to_download(invoices=valid_invoices) + + current_time = datetime.utcnow() + # Tandai faktur sebagai sudah diekspor + valid_invoices.write({ + 'is_efaktur_exported': True, + 'date_efaktur_exported': current_time, # Set tanggal ekspor + }) + + return response diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py new file mode 100644 index 00000000..ff8606b1 --- /dev/null +++ b/indoteknik_custom/models/coretax_fatur.py @@ -0,0 +1,119 @@ +from odoo import models, fields +import xml.etree.ElementTree as ET +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", ) + + 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 : + return '0000000000000000' + + # Hitung jumlah digit + digit_count = len(cleaned_number) + + # Jika jumlah digit kurang dari 15, tambahkan nol di depan + if digit_count < 16: + cleaned_number = cleaned_number.zfill(16) + + return cleaned_number + + def generate_xml(self, invoices=None): + # Buat root XML + root = ET.Element('TaxInvoiceBulk', { + 'xmlns:xsi': "http://www.w3.org/2001/XMLSchema-instance", + 'xsi:noNamespaceSchemaLocation': "TaxInvoice.xsd" + }) + ET.SubElement(root, 'TIN').text = '0742260227086000' + + # 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) + buyerIDTKU = buyerTIN.ljust(len(buyerTIN) + 6, '0') 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, 'TaxInvoiceOpt').text = 'Normal' + ET.SubElement(tax_invoice, 'TrxCode').text = '04' + ET.SubElement(tax_invoice, 'AddInfo') + ET.SubElement(tax_invoice, 'CustomDoc') + ET.SubElement(tax_invoice, 'RefDesc').text = invoice.name + 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, 'BuyerCountry').text = 'IDN' + ET.SubElement(tax_invoice, 'BuyerDocumentNumber').text = 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 + + # 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 + good_service = ET.SubElement(list_of_good_service, 'GoodService') + ET.SubElement(good_service, 'Opt').text = 'A' + ET.SubElement(good_service, 'Code') + 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, '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, 'STLGRate').text = '0' + ET.SubElement(good_service, 'STLG').text = '0' + + # Pretty print XML + xml_str = ET.tostring(root, encoding='utf-8') + xml_pretty = minidom.parseString(xml_str).toprettyxml(indent=" ") + return xml_pretty + + def export_to_download(self, invoices): + # Generate XML content + xml_content = self.generate_xml(invoices) + + # Encode content to Base64 + xml_encoded = base64.b64encode(xml_content.encode('utf-8')) + + # Buat attachment untuk XML + attachment = self.env['ir.attachment'].create({ + 'name': 'Faktur_XML.xml', + 'type': 'binary', + 'datas': xml_encoded, + 'mimetype': 'application/xml', + 'store_fname': 'faktur_xml.xml', + }) + + # Kembalikan URL untuk download dengan header 'Content-Disposition' + response = { + 'type': 'ir.actions.act_url', + 'url': '/web/content/{}?download=true'.format(attachment.id), + 'target': 'self', + } + + return response diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 9007dd71..5bedae13 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -416,7 +416,7 @@ class ProductProduct(models.Model): box_size=5, border=4, ) - qr.add_data(rec.display_name) + qr.add_data(rec.default_code) qr.make(fit=True) img = qr.make_image(fill_color="black", back_color="white") diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 0e39d12a..799c4db0 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -75,6 +75,28 @@ class PurchaseOrder(models.Model): exclude_incoming = fields.Boolean(string='Exclude Incoming', default=False, help='Centang jika tidak mau masuk perhitungan Incoming Qty') not_update_purchasepricelist = fields.Boolean(string='Not Update Purchase Pricelist?') + # total_cost_service = fields.Float(string='Total Cost Service') + # total_delivery_amt = fields.Float(string='Total Delivery Amt') + + # @api.onchange('total_cost_service') + # def _onchange_total_cost_service(self): + # for order in self: + # lines = order.order_line + # if lines: + # # Hitung nilai rata-rata cost_service + # per_line_cost_service = order.total_cost_service / len(lines) + # for line in lines: + # line.cost_service = per_line_cost_service + + # @api.onchange('total_delivery_amt') + # def _onchange_total_delivery_amt(self): + # for order in self: + # lines = order.order_line + # if lines: + # # Hitung nilai rata-rata delivery_amt + # per_line_delivery_amt = order.total_delivery_amt / len(lines) + # for line in lines: + # line.delivery_amt = per_line_delivery_amt def _compute_total_margin_match(self): for purchase in self: @@ -115,6 +137,7 @@ class PurchaseOrder(models.Model): 'ref': self.name, 'invoice_date': current_date, 'date': current_date, + 'invoice_origin': self.name, 'move_type': 'in_invoice' } @@ -165,6 +188,11 @@ class PurchaseOrder(models.Model): self.bills_pelunasan_id = bills.id + lognote_message = ( + f"Vendor bill created from: {self.name} ({self.partner_ref})" + ) + bills.message_post(body=lognote_message) + return { 'name': _('Account Move'), 'view_mode': 'tree,form', @@ -174,12 +202,10 @@ class PurchaseOrder(models.Model): 'domain': [('id', '=', bills.id)] } - - def create_bill_dp(self): if not self.env.user.is_accounting: raise UserError('Hanya Accounting yang bisa bikin bill dp') - + current_date = datetime.utcnow() data_bills = { 'partner_id': self.partner_id.id, @@ -187,8 +213,8 @@ class PurchaseOrder(models.Model): 'ref': self.name, 'invoice_date': current_date, 'date': current_date, + 'invoice_origin': self.name, 'move_type': 'in_invoice' - } bills = self.env['account.move'].create([data_bills]) @@ -197,14 +223,13 @@ class PurchaseOrder(models.Model): data_line_bills = { 'move_id': bills.id, - 'product_id': product_dp.id, #product down payment - 'account_id': 401, #Uang Muka persediaan barang dagang + 'product_id': product_dp.id, # product down payment + 'account_id': 401, # Uang Muka persediaan barang dagang 'quantity': 1, 'product_uom_id': 1, 'tax_ids': [line[0].taxes_id.id for line in self.order_line], } - bills_line = self.env['account.move.line'].create([data_line_bills]) self.bills_dp_id = bills.id @@ -213,6 +238,11 @@ class PurchaseOrder(models.Model): move_line.name = '[IT.121456] Down Payment' move_line.partner_id = self.partner_id.id + lognote_message = ( + f"Vendor bill created from: {self.name} ({self.partner_ref})" + ) + bills.message_post(body=lognote_message) + return { 'name': _('Account Move'), 'view_mode': 'tree,form', diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 2b448874..2ef1f43d 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1031,8 +1031,9 @@ class SaleOrder(models.Model): if self.have_outstanding_invoice: raise UserError("Invoice harus di Cancel dahulu") - elif self.have_outstanding_picking: - raise UserError("DO harus di Cancel dahulu") + for line in self.order_line: + if line.qty_delivered > 0: + raise UserError("DO harus di-cancel terlebih dahulu.") if not self.web_approval: self.web_approval = 'company' @@ -1410,4 +1411,16 @@ class SaleOrder(models.Model): 'npwp': partner.npwp, 'email': partner.email, 'customer_type': partner.customer_type, - })
\ No newline at end of file + }) + + def write(self, vals): + for order in self: + if order.state in ['sale', 'cancel']: + if 'order_line' in vals: + new_lines = vals.get('order_line', []) + for command in new_lines: + 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) + return res
\ No newline at end of file diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index a82bcc8a..5b990ed0 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -38,6 +38,14 @@ class SaleOrderLine(models.Model): md_vendor_id = fields.Many2one('res.partner', string='MD Vendor', readonly=True) margin_md = fields.Float(string='Margin MD') qty_free_bu = fields.Float(string='Free BU', compute='_get_qty_free_bandengan') + desc_updatable = fields.Boolean(string='desc boolean', default=False, compute='_get_desc_updatable') + + def _get_desc_updatable(self): + for line in self: + if line.product_id.id != 417724: + line.desc_updatable = False + else: + line.desc_updatable = True def _get_qty_free_bandengan(self): for line in self: @@ -272,6 +280,10 @@ class SaleOrderLine(models.Model): (line.product_id.short_spesification if line.product_id.short_spesification else '') line.name = line_name line.weight = line.product_id.weight + if line.product_id.id != 417724: + line.desc_updatable = False + else: + line.desc_updatable = True @api.constrains('vendor_id') def _check_vendor_id(self): @@ -378,4 +390,14 @@ class SaleOrderLine(models.Model): if not line.product_id.product_tmpl_id.sale_ok: raise UserError('Product %s belum bisa dijual, harap hubungi finance' % line.product_id.display_name) if not line.vendor_id or not line.purchase_price and not line.display_type == 'line_note': - raise UserError(_('Isi Vendor dan Harga Beli sebelum Request Approval'))
\ No newline at end of file + raise UserError(_('Isi Vendor dan Harga Beli sebelum Request Approval')) + + @api.depends('state') + def _compute_product_updatable(self): + for line in self: + if line.state == 'draft': + line.product_updatable = True + # line.desc_updatable = True + else: + line.product_updatable = False + # line.desc_updatable = False diff --git a/indoteknik_custom/models/stock_immediate_transfer.py b/indoteknik_custom/models/stock_immediate_transfer.py new file mode 100644 index 00000000..4be0dff2 --- /dev/null +++ b/indoteknik_custom/models/stock_immediate_transfer.py @@ -0,0 +1,36 @@ +from odoo import models, api, _ +from odoo.exceptions import UserError + +class StockImmediateTransfer(models.TransientModel): + _inherit = 'stock.immediate.transfer' + + def process(self): + """Override process method to add send_mail_bills logic.""" + pickings_to_do = self.env['stock.picking'] + pickings_not_to_do = self.env['stock.picking'] + + for line in self.immediate_transfer_line_ids: + if line.to_immediate is True: + pickings_to_do |= line.picking_id + else: + pickings_not_to_do |= line.picking_id + + for picking in pickings_to_do: + picking.send_mail_bills() + # If still in draft => confirm and assign + if picking.state == 'draft': + picking.action_confirm() + if picking.state != 'assigned': + picking.action_assign() + if picking.state != 'assigned': + raise UserError(_("Could not reserve all requested products. Please use the 'Mark as Todo' button to handle the reservation manually.")) + for move in picking.move_lines.filtered(lambda m: m.state not in ['done', 'cancel']): + for move_line in move.move_line_ids: + move_line.qty_done = move_line.product_uom_qty + + pickings_to_validate = self.env.context.get('button_validate_picking_ids') + if pickings_to_validate: + pickings_to_validate = self.env['stock.picking'].browse(pickings_to_validate) + pickings_to_validate = pickings_to_validate - pickings_not_to_do + return pickings_to_validate.with_context(skip_immediate=True).button_validate() + return True diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index e25704f4..cd330aeb 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -6,10 +6,17 @@ from itertools import groupby import pytz, requests, json, requests from dateutil import parser import datetime - +import hmac +import hashlib +import base64 +import requests +import time +import logging +_logger = logging.getLogger(__name__) class StockPicking(models.Model): _inherit = 'stock.picking' + # check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True) is_internal_use = fields.Boolean('Internal Use', help='flag which is internal use or not') account_id = fields.Many2one('account.account', string='Account') efaktur_id = fields.Many2one('vit.efaktur', string='Faktur Pajak') @@ -147,6 +154,86 @@ class StockPicking(models.Model): envio_latest_longitude = fields.Float(string="Log Longitude", readonly=True) tracking_by = fields.Many2one('res.users', string='Tracking By', readonly=True, tracking=True) + # Lalamove Section + lalamove_order_id = fields.Char(string="Lalamove Order ID", copy=False) + lalamove_address = fields.Char(string="Lalamove Address") + lalamove_name = fields.Char(string="Lalamove Name") + lalamove_phone = fields.Char(string="Lalamove Phone") + lalamove_status = fields.Char(string="Lalamove Status") + lalamove_delivered_at = fields.Datetime(string="Lalamove Delivered At") + lalamove_data = fields.Text(string="Lalamove Data", readonly=True) + lalamove_image_url = fields.Char(string="Lalamove Image URL") + lalamove_image_html = fields.Html(string="Lalamove Image", compute="_compute_lalamove_image_html") + + def _compute_lalamove_image_html(self): + for record in self: + if record.lalamove_image_url: + record.lalamove_image_html = f'<img src="{record.lalamove_image_url}" width="300" height="300"/>' + else: + record.lalamove_image_html = "No image available." + + def action_fetch_lalamove_order(self): + pickings = self.env['stock.picking'].search([ + ('picking_type_code', '=', 'outgoing'), + ('state', '=', 'done'), + ('carrier_id', '=', 9) + ]) + for picking in pickings: + try: + order_id = picking.lalamove_order_id + apikey = self.env['ir.config_parameter'].sudo().get_param('lalamove.apikey') + secret = self.env['ir.config_parameter'].sudo().get_param('lalamove.secret') + market = self.env['ir.config_parameter'].sudo().get_param('lalamove.market', default='ID') + + order_data = picking.get_lalamove_order(order_id, apikey, secret, market) + picking.lalamove_data = order_data + except Exception as e: + _logger.error(f"Error fetching Lalamove order for picking {picking.id}: {str(e)}") + continue + + def get_lalamove_order(self, order_id, apikey, secret, market): + timestamp = str(int(time.time() * 1000)) + message = f"{timestamp}\r\nGET\r\n/v3/orders/{order_id}\r\n\r\n" + signature = hmac.new(secret.encode('utf-8'), message.encode('utf-8'), hashlib.sha256).hexdigest() + + headers = { + "Content-Type": "application/json", + "Authorization": f"hmac {apikey}:{timestamp}:{signature}", + "Market": market + } + + url = f"https://rest.lalamove.com/v3/orders/{order_id}" + response = requests.get(url, headers=headers) + + if response.status_code == 200: + data = response.json() + stops = data.get("data", {}).get("stops", []) + + for stop in stops: + pod = stop.get("POD", {}) + if pod.get("status") == "DELIVERED": + image_url = pod.get("image") # Sesuaikan jika key berbeda + self.lalamove_image_url = image_url + + address = stop.get("address") + name = stop.get("name") + phone = stop.get("phone") + delivered_at = pod.get("deliveredAt") + + delivered_at_dt = self._convert_to_datetime(delivered_at) + + self.lalamove_address = address + self.lalamove_name = name + self.lalamove_phone = phone + self.lalamove_status = pod.get("status") + self.lalamove_delivered_at = delivered_at_dt + return data + + raise UserError("No delivered data found in Lalamove response.") + else: + raise UserError(f"Error {response.status_code}: {response.text}") + + def _convert_to_wib(self, date_str): """ Mengonversi string waktu ISO 8601 ke format waktu Indonesia (WIB) @@ -674,13 +761,13 @@ class StockPicking(models.Model): if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': if self.origin and 'Return of' in self.origin: raise UserError("Button ini hanya untuk Logistik") - + if self.picking_type_code == 'internal': self.check_qty_done_stock() if self._name != 'stock.picking': return super(StockPicking, self).button_validate() - + if not self.picking_code: self.picking_code = self.env['ir.sequence'].next_by_code('stock.picking.code') or '0' @@ -694,15 +781,10 @@ class StockPicking(models.Model): raise UserError("Harus di Approve oleh Accounting") if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver: - raise UserError("Harus di Approve oleh Logistik") + raise UserError("Harus di Approve oleh Logistik") if self.location_dest_id.id == 47 and not self.env.user.is_purchasing_manager: - raise UserError("Transfer ke gudang selisih harus di approve Rafly Hanggara") - - # if self.group_id.sale_id: - # if self.group_id.sale_id.payment_link_midtrans: - # if self.group_id.sale_id.payment_status != 'settlement' and self.group_id.sale_id.state == 'draft': - # raise UserError('Uang belum masuk (settlement), mohon konfirmasi ke sales atau finance') + raise UserError("Transfer ke gudang selisih harus di approve Rafly Hanggara") if self.is_internal_use: self.approval_status = 'approved' @@ -720,7 +802,7 @@ class StockPicking(models.Model): if not self.date_reserved: current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') self.date_reserved = current_time - + self.validation_minus_onhand_quantity() self.responsible = self.env.user.id res = super(StockPicking, self).button_validate() @@ -728,6 +810,59 @@ class StockPicking(models.Model): self.date_done = datetime.datetime.utcnow() self.state_reserve = 'done' return res + + + def send_mail_bills(self): + if self.picking_type_code == 'incoming' and self.purchase_id: + template = self.env.ref('indoteknik_custom.mail_template_invoice_po_document') + if template and self.purchase_id: + # Render email body + email_values = template.sudo().generate_email( + res_ids=[self.purchase_id.id], + fields=['body_html'] + ) + rendered_body = email_values.get(self.purchase_id.id, {}).get('body_html', '') + + # Render report dengan XML ID + report = self.env.ref('purchase.action_report_purchase_order') # Gunakan XML ID laporan + if not report: + raise UserError("Laporan dengan XML ID 'purchase.action_report_purchase_order' tidak ditemukan.") + + # Render laporan ke PDF + pdf_content, _ = report._render_qweb_pdf([self.purchase_id.id]) + report_content = base64.b64encode(pdf_content).decode('utf-8') + + # Kirim email menggunakan template + email_sent = template.sudo().send_mail(self.purchase_id.id, force_send=True) + + if email_sent: + # Buat attachment untuk laporan + attachment = self.env['ir.attachment'].create({ + 'name': self.purchase_id.name or "Laporan Invoice.pdf", + 'type': 'binary', + 'datas': report_content, + 'res_model': 'purchase.order', + 'res_id': self.purchase_id.id, + 'mimetype': 'application/pdf', + }) + + # Siapkan data untuk mail.compose.message + compose_values = { + 'subject': "Pengiriman Email Invoice", + 'body': rendered_body, + 'attachment_ids': [(4, attachment.id)], + 'res_id': self.purchase_id.id, + 'model': 'purchase.order', + } + + # Buat mail.compose.message + compose_message = self.env['mail.compose.message'].create(compose_values) + + # Kirim pesan melalui wizard + compose_message.action_send_mail() + + return True + def action_cancel(self): if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': if self.origin and 'Return of' in self.origin: @@ -871,4 +1006,50 @@ class StockPicking(models.Model): formatted_fastest_eta = fastest_eta.strftime(format_time_fastest) formatted_longest_eta = longest_eta.strftime(format_time) - return f'{formatted_fastest_eta} - {formatted_longest_eta}'
\ No newline at end of file + return f'{formatted_fastest_eta} - {formatted_longest_eta}' + +# class CheckProduct(models.Model): +# _name = 'check.product' +# _description = 'Check Product' +# _order = 'picking_id, id' + +# picking_id = fields.Many2one('stock.picking', string='Picking Reference', required=True, ondelete='cascade', index=True, copy=False) +# product_id = fields.Many2one('product.product', string='Product') + + +# @api.constrains('product_id') +# def check_product_validity(self): +# """ +# Validate if the product exists in the related stock.picking's move_ids_without_package +# and ensure that the product's quantity does not exceed the available product_uom_qty. +# """ +# for record in self: +# if not record.picking_id or not record.product_id: +# continue + +# # Filter move lines in the related picking for the selected product +# moves = record.picking_id.move_ids_without_package.filtered( +# lambda move: move.product_id.id == record.product_id.id +# ) + +# if not moves: +# raise UserError(( +# "The product '%s' is not available in the related stock picking's moves. " +# "Please check and try again." +# ) % record.product_id.display_name) + +# # Calculate the total entries for the product in check.product for the same picking +# product_entries_count = self.search_count([ +# ('picking_id', '=', record.picking_id.id), +# ('product_id', '=', record.product_id.id) +# ]) + +# # Sum the product_uom_qty for all relevant moves +# total_qty_in_moves = sum(moves.mapped('product_uom_qty')) + +# # Compare the count of entries against the available quantity +# if product_entries_count > total_qty_in_moves: +# raise UserError(( +# "The product '%s' exceeds the allowable quantity (%s) in the related stock picking's moves. " +# "You can only add it %s times." +# ) % (record.product_id.display_name, total_qty_in_moves, total_qty_in_moves)) diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index c237f417..f5261cd4 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -334,7 +334,7 @@ class UserPengajuanTempoRequest(models.Model): @api.onchange('tempo_duration') def _tempo_duration_change(self, vals): for tempo in self: - if tempo.env.user.id not in (7, 377): + if tempo.env.user.id not in (7, 377, 12182): raise UserError("Durasi tempo hanya bisa di ubah oleh Sales Manager atau Direktur") @api.onchange('tempo_limit') @@ -350,8 +350,8 @@ 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 != 377: - if tempo.env.user.id != 12182: + if tempo.env.user.id not in (377, 12182): + # if tempo.env.user.id != 12182: raise UserError("Pengajuan tempo hanya bisa di approve oleh sales manager") else: return { @@ -369,8 +369,8 @@ class UserPengajuanTempoRequest(models.Model): if tempo.env.user.id == 7: raise UserError("Pengajuan tempo harus di approve oleh Finence terlebih dahulu") else: - # if tempo.env.user.id not in (688, 28): - if tempo.env.user.id not in (101,288,28,12182): + if tempo.env.user.id not in (688, 28, 12182): + # if tempo.env.user.id not in (288,28,12182): raise UserError("Pengajuan tempo hanya bisa di approve oleh Finence") else: return { @@ -385,8 +385,8 @@ class UserPengajuanTempoRequest(models.Model): } elif tempo.state_tempo == 'approval_finance': - # if tempo.env.user.id != 7: - if tempo.env.user.id != 12182: + if tempo.env.user.id != 7: + # if tempo.env.user.id != 12182: raise UserError("Pengajuan tempo hanya bisa di approve oleh Direktur") else: return { diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index d4a1ea90..f53a360f 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -149,6 +149,8 @@ access_sales_order_fulfillment_v2,access.sales.order.fulfillment.v2,model_sales_ access_v_move_outstanding,access.v.move.outstanding,model_v_move_outstanding,,1,1,1,1 access_va_multi_approve,access.va.multi.approve,model_va_multi_approve,,1,1,1,1 access_va_multi_reject,access.va.multi.reject,model_va_multi_reject,,1,1,1,1 +access_stock_immediate_transfer,access.stock.immediate.transfer,model_stock_immediate_transfer,,1,1,1,1 +access_coretax_faktur,access.coretax.faktur,model_coretax_faktur,,1,1,1,1 access_User_pengajuan_tempo_line,access.user.pengajuan.tempo.line,model_user_pengajuan_tempo_line,,1,1,1,1 access_user_pengajuan_tempo,access.user.pengajuan.tempo,model_user_pengajuan_tempo,,1,1,1,1 diff --git a/indoteknik_custom/views/account_move.xml b/indoteknik_custom/views/account_move.xml index 2863af57..4cc35b6d 100644 --- a/indoteknik_custom/views/account_move.xml +++ b/indoteknik_custom/views/account_move.xml @@ -163,5 +163,13 @@ <field name="state">code</field> <field name="code">action = records.open_form_multi_create_reklas_penjualan()</field> </record> + + <record id="action_export_faktur" model="ir.actions.server"> + <field name="name">Export Faktur ke XML</field> + <field name="model_id" ref="account.model_account_move" /> + <field name="binding_model_id" ref="account.model_account_move" /> + <field name="state">code</field> + <field name="code">action = records.export_faktur_to_xml()</field> + </record> </data> </odoo>
\ No newline at end of file diff --git a/indoteknik_custom/views/coretax_faktur.xml b/indoteknik_custom/views/coretax_faktur.xml new file mode 100644 index 00000000..45eea23f --- /dev/null +++ b/indoteknik_custom/views/coretax_faktur.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <data> + <record id="act_coretax_faktur" model="ir.actions.act_window"> + <field name="name">Export Faktur Pajak Keluaran XML Version</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">coretax.faktur</field> + <field name="view_mode">form</field> + <field name="target">new</field> + </record> + + <record id="view_coretax_faktur" model="ir.ui.view"> + <field name="name">view coretax faktur</field> + <field name="model">coretax.faktur</field> + <field name="type">form</field> + <field name="arch" type="xml"> + <form string="Export Pajak Keluaran"> + <p> + Klik tombol Export di bawah untuk mulai export Faktur Pajak Keluaran. + Data yang diexport adalah Customer Invoice yang berstatus Open dan belum diexport ke E-Faktur. + </p> + + <p> + Setelah proses export Faktur Pajak Keluaran selesai dilakukan, + download file ini: + </p> + <group> + <field name="export_file" filename="export_filename" readonly="1"/> + </group> + + <p> + Lalu import ke program E-Faktur DJP melalui menu <b>Referensi - Pajak Keluaran - Import</b> + </p> + + <footer> + <button string="Export" name="export_to_download" type="object" class="btn-primary"/> + <button string="Cancel" class="btn-default" special="cancel"/> + </footer> + </form> + </field> + </record> + + <menuitem id="coretax_faktur_export" + parent="vit_efaktur.menu_vit_efaktur_keluaran" + sequence="80" + name="Export FP. Keluaran XML" + action="act_coretax_faktur" /> + </data> +</odoo>
\ No newline at end of file diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 703b4d49..3539dbf3 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -107,6 +107,14 @@ {'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"/> + </xpath> + <xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='name']" position="attributes"> + <attribute name="attrs"> + {'readonly': [('desc_updatable', '=', False)]} + </attribute> + </xpath> <xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_unit']" position="attributes"> <attribute name="attrs"> { diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 26fe7c1e..8acba608 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -61,6 +61,11 @@ type="object" attrs="{'invisible': [('carrier_id', '!=', 151)]}" /> + <button name="action_fetch_lalamove_order" + string="Tracking Lalamove" + type="object" + attrs="{'invisible': [('carrier_id', '!=', 9)]}" + /> </button> <field name="backorder_id" position="after"> <field name="summary_qty_detail"/> @@ -81,6 +86,9 @@ <field name="partner_id" position="after"> <field name="real_shipping_id"/> </field> + <field name="product_uom_qty" position="attributes"> + <attribute name="attrs">{'readonly': [('parent.picking_type_code', '=', 'incoming')]}</attribute> + </field> <field name="date_done" position="after"> <field name="arrival_time"/> </field> @@ -152,7 +160,7 @@ <field name="sj_documentation" widget="image" /> <field name="paket_documentation" widget="image" /> </group> - <group> + <group attrs="{'invisible': [('carrier_id', '!=', 151)]}"> <field name="envio_id" invisible="1"/> <field name="envio_code"/> <field name="envio_ref_code"/> @@ -170,6 +178,17 @@ <field name="envio_latest_longitude" invisible="1"/> <field name="tracking_by" invisible="1"/> </group> + <group attrs="{'invisible': [('carrier_id', '!=', 9)]}"> + <field name="lalamove_data" invisible="1"/> + <field name="lalamove_order_id"/> + <field name="lalamove_address"/> + <field name="lalamove_name"/> + <field name="lalamove_phone"/> + <field name="lalamove_status"/> + <field name="lalamove_delivered_at"/> + <field name="lalamove_image_url" invisible="1"/> + <field name="lalamove_image_html"/> + </group> </group> </page> <!-- <page string="Check Product" name="check_product"> @@ -184,8 +203,10 @@ <field name="name">check.product.tree</field> <field name="model">check.product</field> <field name="arch" type="xml"> - <tree editable="bottom"> + <tree editable="bottom" decoration-warning="status == 'Pending'" decoration-success="status == 'Done'"> <field name="product_id"/> + <field name="quantity"/> + <field name="status"/> </tree> </field> </record> --> diff --git a/indoteknik_custom/views/user_pengajuan_tempo.xml b/indoteknik_custom/views/user_pengajuan_tempo.xml index 5b9e51cc..f9e747b3 100644 --- a/indoteknik_custom/views/user_pengajuan_tempo.xml +++ b/indoteknik_custom/views/user_pengajuan_tempo.xml @@ -202,8 +202,8 @@ <field name="subject">Pengajuan Tempo Harus di Periksa!</field> <field name="email_from">"Indoteknik.com" <noreply@indoteknik.com></field> <field name="reply_to">sales@indoteknik.com</field> -<!-- <field name="email_to">vita@indoteknik.co.id</field>--> - <field name="email_to">sapiabon768@gmail.com</field> + <field name="email_to">vita@indoteknik.co.id</field> +<!-- <field name="email_to">sapiabon768@gmail.com</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;"> <tr><td align="center"> @@ -269,8 +269,8 @@ <field name="subject">Pengajuan Tempo Harus di Periksa!</field> <field name="email_from">"Indoteknik.com" <noreply@indoteknik.com></field> <field name="reply_to">sales@indoteknik.com</field> -<!-- <field name="email_to">widyariyanti97@gmail.com, stephan@indoteknik.co.id</field>--> - <field name="email_to">sapiabon768@gmail.com</field> + <field name="email_to">widyariyanti97@gmail.com, stephan@indoteknik.co.id</field> +<!-- <field name="email_to">sapiabon768@gmail.com</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;"> <tr><td align="center"> diff --git a/indoteknik_custom/views/user_pengajuan_tempo_request.xml b/indoteknik_custom/views/user_pengajuan_tempo_request.xml index 7ba87184..bb8262c9 100644 --- a/indoteknik_custom/views/user_pengajuan_tempo_request.xml +++ b/indoteknik_custom/views/user_pengajuan_tempo_request.xml @@ -200,8 +200,8 @@ <field name="subject">Pengajuan Tempo Harus di Periksa!</field> <field name="email_from">"Indoteknik.com" <noreply@indoteknik.com></field> <field name="reply_to">sales@indoteknik.com</field> -<!-- <field name="email_to">akbar@fixcomart.co.id</field>--> - <field name="email_to">sapiabon768@gmail.com</field> + <field name="email_to">akbar@fixcomart.co.id</field> +<!-- <field name="email_to">sapiabon768@gmail.com</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;"> <tr><td align="center"> |
