diff options
| author | Azka Nathan <darizkyfaz@gmail.com> | 2025-03-05 14:02:13 +0700 |
|---|---|---|
| committer | Azka Nathan <darizkyfaz@gmail.com> | 2025-03-05 14:02:13 +0700 |
| commit | 006c1171970a3ade5033d8bb112b7f6094b35d11 (patch) | |
| tree | ff11743c553ebb6682b0640171318d9b322d427c | |
| parent | 660913a45a1efe08f308d405e1011efc9744c553 (diff) | |
| parent | 92b6da28414fed56732f86e1f04ea2fac3464d7d (diff) | |
Merge branch 'odoo-production' into dev/wms
# Conflicts:
# indoteknik_custom/models/__init__.py
# indoteknik_custom/models/stock_immediate_transfer.py
# indoteknik_custom/models/stock_picking.py
58 files changed, 1220 insertions, 217 deletions
diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py index 8b95ade8..a7e027c8 100644 --- a/indoteknik_api/controllers/api_v1/sale_order.py +++ b/indoteknik_api/controllers/api_v1/sale_order.py @@ -394,7 +394,8 @@ class SaleOrder(controller.Controller): # Fetch partner details sales_partner = request.env['res.partner'].browse(params['value']['partner_id']) - + partner_invoice = request.env['res.partner'].browse(params['value']['partner_invoice_id']) + main_partner = partner_invoice.get_main_parent() parameters = { 'warehouse_id': 8, 'carrier_id': 1, @@ -410,7 +411,7 @@ class SaleOrder(controller.Controller): 'partner_id': params['value']['partner_id'], 'partner_shipping_id': params['value']['partner_shipping_id'], 'real_shipping_id': params['value']['partner_shipping_id'], - 'partner_invoice_id': params['value']['partner_invoice_id'], + 'partner_invoice_id': main_partner.id, 'real_invoice_id': params['value']['partner_invoice_id'], 'partner_purchase_order_name': params['value']['po_number'], 'partner_purchase_order_file': params['value']['po_file'], @@ -426,7 +427,7 @@ class SaleOrder(controller.Controller): 'npwp': sales_partner.npwp or '0', # Get NPWP from partner 'sppkp': sales_partner.sppkp, # Get SPPKP from partner 'email': sales_partner.email, # Get Email from partner - 'user_id': 3222 # User ID: Nadia Rauhadatul Firdaus + 'user_id': 11314 # User ID: Boy Revandi } sales_partner = request.env['res.partner'].browse(parameters['partner_id']) diff --git a/indoteknik_api/controllers/api_v1/stock_picking.py b/indoteknik_api/controllers/api_v1/stock_picking.py index 110cde8a..2e0c4ad0 100644 --- a/indoteknik_api/controllers/api_v1/stock_picking.py +++ b/indoteknik_api/controllers/api_v1/stock_picking.py @@ -57,7 +57,7 @@ class StockPicking(controller.Controller): if params['status'] == 'pending': domain += pending_domain elif params['status'] == 'shipment': - domain += shipment_domain + domain += shipment_domain + shipment_domain2 elif params['status'] == 'completed': domain += completed_domain diff --git a/indoteknik_api/controllers/api_v1/user.py b/indoteknik_api/controllers/api_v1/user.py index f651d9fa..c0974367 100644 --- a/indoteknik_api/controllers/api_v1/user.py +++ b/indoteknik_api/controllers/api_v1/user.py @@ -98,7 +98,7 @@ class User(controller.Controller): user.partner_id.npwp = '00.000.000.0-000.000' user.partner_id.sppkp = '-' user.partner_id.nama_wajib_pajak = user.name - user.partner_id.user_id = 3222 + user.partner_id.user_id = 11314 user.partner_id.property_account_receivable_id = 395 user.partner_id.property_account_payable_id = 438 data = { @@ -130,6 +130,7 @@ class User(controller.Controller): alamat_bisnis = kw.get('alamat_bisnis', False) nama_wajib_pajak = kw.get('nama_wajib_pajak', False) is_pkp = kw.get('is_pkp') + is_terdaftar = kw.get('is_terdaftar', False) type_acc = kw.get('type_acc', 'individu') or 'individu' if not name or not email or not password: @@ -168,18 +169,19 @@ class User(controller.Controller): if type_acc == 'business' and business_name: # Eksekusi query SQL menggunakan Levenshtein distance query = """ - SELECT name, levenshtein(name::text, %s) AS distance + SELECT id, name, levenshtein(name::text, %s) AS distance FROM res_partner - WHERE levenshtein(name::text, %s) < 3 + WHERE is_company = true AND active = true + AND levenshtein(name::text, %s) < 3 ORDER BY distance ASC """ params = (business_name, business_name) request.env.cr.execute(query, params) result = request.env.cr.fetchone() - if result: - match_company_name = result[0] - match_company_id = result[2] + if result and is_terdaftar: + match_company_name = result[2] + match_company_id = result[0] # Create a user company request request.env['user.company.request'].create({ @@ -206,7 +208,7 @@ class User(controller.Controller): 'email': email_partner, 'street': alamat_bisnis, 'company_type': 'company', - 'user_id': 3222, + 'user_id': 11314, 'property_account_receivable_id': 395, 'property_account_payable_id': 438, 'active': False, @@ -251,7 +253,7 @@ class User(controller.Controller): user.partner_id.npwp = '00.000.000.0-000.000' user.partner_id.sppkp = '-' user.partner_id.nama_wajib_pajak = name - user.partner_id.user_id = 3222 + user.partner_id.user_id = 11314 user.partner_id.property_account_receivable_id= 395 user.partner_id.property_account_payable_id = 438 @@ -603,7 +605,7 @@ class User(controller.Controller): 'email': email_partner, 'street': alamat_bisnis, 'company_type': 'company', - 'user_id': 3222, + 'user_id': 11314, 'property_account_receivable_id': 395, 'property_account_payable_id': 438, 'active': False, diff --git a/indoteknik_api/controllers/api_v1/voucher.py b/indoteknik_api/controllers/api_v1/voucher.py index 910488d1..9ffeeace 100644 --- a/indoteknik_api/controllers/api_v1/voucher.py +++ b/indoteknik_api/controllers/api_v1/voucher.py @@ -22,9 +22,14 @@ class Voucher(controller.Controller): code = kw.get('code') type = kw.get('type') user_id = int(kw.get('user_id', 0)) + partner_id = int(kw.get('partner_id', 0)) source = kw.get('source') visibility = ['public'] + user = request.env['res.users'].search([('id', '=', user_id)], limit=1) + if not user: + return self.response([]) + domain = [] if code: visibility.append('private') @@ -37,14 +42,19 @@ class Voucher(controller.Controller): type = type.split(',') domain += [('apply_type', 'in', type)] + if partner_id: + partner = request.env['res.partner'].search([('id', '=', partner_id)], limit=1) + main_parent = partner.get_main_parent() + if main_parent and main_parent.company_type: + domain += [('account_type', 'in', ['all',main_parent.company_type])] + # domain += [('account_type', 'in', main_parent.company_type)] + + domain += [('visibility', 'in', visibility)] vouchers = request.env['voucher'].get_active_voucher(domain) checkout = cart.get_user_checkout(user_id, source=source) products = checkout['products'] - user = request.env['res.users'].search([('id', '=', user_id)], limit=1) - if not user: - return self.response([]) order_line = [] for product in products: @@ -89,3 +99,9 @@ class Voucher(controller.Controller): sorted_results = sorted(results, key=lambda x: x['can_apply'], reverse=True) return self.response(sorted_results) + + def get_user_by_email(self, email): + return request.env['res.users'].search([ + ('login', '=', email), + ('active', 'in', [True, False]) + ])
\ No newline at end of file diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 67a41a08..f66314fa 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -60,6 +60,7 @@ 'views/website_brand_homepage.xml', 'views/website_categories_homepage.xml', 'views/website_categories_management.xml', + 'views/website_telegram.xml', 'views/website_categories_lob.xml', 'views/sales_target.xml', 'views/purchase_outstanding.xml', @@ -117,6 +118,7 @@ 'views/sale_monitoring_detail_v2.xml', 'views/purchase_order_multi_update.xml', 'views/purchase_order_multi_confirm.xml', + 'views/purchase_order_multi_ask_approval.xml', 'views/invoice_reklas_penjualan.xml', 'views/po_multi_cancel.xml', 'views/logbook_sj.xml', @@ -160,6 +162,7 @@ 'report/report_picking.xml', 'report/report_sale_order.xml', 'views/coretax_faktur.xml', + 'views/stock_inventory.xml', ], 'demo': [], 'css': [], diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index a5297806..531767fe 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -106,6 +106,7 @@ from . import sale_monitoring_detail_v2 from . import purchase_order_multi_update from . import invoice_reklas_penjualan from . import purchase_order_multi_confirm +from . import purchase_order_multi_ask_approval from . import po_multi_cancel from . import logbook_sj from . import report_logbook_sj @@ -131,6 +132,7 @@ from . import account_tax from . import approval_unreserve from . import vendor_approval from . import partner +from . import website_telegram from . import find_page from . import user_pengajuan_tempo_line from . import user_pengajuan_tempo @@ -139,6 +141,9 @@ from . import va_multi_approve from . import va_multi_reject from . import stock_immediate_transfer from . import coretax_fatur +from . import ir_actions_report from . import barcoding_product from . import sales_order_koli from . import stock_backorder_confirmation +from . import account_payment_register +from . import stock_inventory diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 4553e446..9aa0743b 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -63,6 +63,20 @@ class AccountMove(models.Model): flag_delivery_amt = fields.Boolean(string="Flag Delivery Amount", compute='compute_flag_delivery_amt') nomor_kwitansi = fields.Char(string="Nomor Kwitansi") other_subtotal = fields.Float(string="Other Subtotal", compute='compute_other_subtotal') + 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') + + def _update_line_name_from_ref(self): + """Update all account.move.line name fields with ref from account.move""" + for move in self: + if move.move_type == 'entry' and move.ref and move.line_ids: + for line in move.line_ids: + line.name = move.ref + + def compute_other_taxes(self): + for rec in self: + rec.other_taxes = rec.other_subtotal * 0.12 def compute_other_subtotal(self): for rec in self: @@ -102,6 +116,7 @@ class AccountMove(models.Model): def create(self, vals): vals['nomor_kwitansi'] = self.env['ir.sequence'].next_by_code('nomor.kwitansi') or '0' result = super(AccountMove, self).create(vals) + # result._update_line_name_from_ref() return result def compute_so_shipping_paid_by(self): @@ -353,7 +368,7 @@ class AccountMove(models.Model): def export_faktur_to_xml(self): - valid_invoices = self.validate_faktur_for_export() + valid_invoices = self # Panggil model coretax.faktur untuk menghasilkan XML coretax_faktur = self.env['coretax.faktur'].create({}) diff --git a/indoteknik_custom/models/account_move_due_extension.py b/indoteknik_custom/models/account_move_due_extension.py index 6fc58cdd..c48c2372 100644 --- a/indoteknik_custom/models/account_move_due_extension.py +++ b/indoteknik_custom/models/account_move_due_extension.py @@ -90,6 +90,7 @@ class DueExtension(models.Model): return self.order_id._create_approval_notification('Pimpinan') if self.order_id._requires_approval_margin_manager(): + self.order_id.check_credit_limit() self.order_id.approval_status = 'pengajuan1' return self.order_id._create_approval_notification('Sales Manager') diff --git a/indoteknik_custom/models/account_move_line.py b/indoteknik_custom/models/account_move_line.py index a4b25109..7c95d4ef 100644 --- a/indoteknik_custom/models/account_move_line.py +++ b/indoteknik_custom/models/account_move_line.py @@ -7,6 +7,7 @@ class AccountMoveLine(models.Model): cost_centre_id = fields.Many2one('cost.centre', string='Cost Centre') is_required = fields.Boolean(string='Is Required', compute='_compute_is_required') analytic_account_ids = fields.Many2many('account.analytic.account', string='Analytic Account') + line_no = fields.Integer('No', default=0) @api.onchange('account_id') def _onchange_account_id(self): @@ -22,3 +23,11 @@ class AccountMoveLine(models.Model): else: account.is_required = False + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if 'move_id' in vals: + move = self.env['account.move'].browse(vals['move_id']) + if move.move_type == 'entry' and move.is_hr == True: + vals['name'] = move.ref + return super().create(vals_list)
\ No newline at end of file diff --git a/indoteknik_custom/models/account_payment_register.py b/indoteknik_custom/models/account_payment_register.py new file mode 100644 index 00000000..4ebb7e4e --- /dev/null +++ b/indoteknik_custom/models/account_payment_register.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api, _ +from odoo.exceptions import UserError + + +class AccountPaymentRegister(models.TransientModel): + _inherit = 'account.payment.register' + def _create_payment_vals_from_wizard(self): + payment_vals = { + 'date': self.payment_date, + 'amount': self.amount, + 'payment_type': self.payment_type, + 'partner_type': self.partner_type, + 'ref': self.communication, + 'journal_id': self.journal_id.id, + 'is_hr': True, + 'currency_id': self.currency_id.id, + 'partner_id': self.partner_id.id, + 'partner_bank_id': self.partner_bank_id.id, + 'payment_method_id': self.payment_method_id.id, + 'destination_account_id': self.line_ids[0].account_id.id + } + + if not self.currency_id.is_zero(self.payment_difference) and self.payment_difference_handling == 'reconcile': + payment_vals['write_off_line_vals'] = { + 'name': self.writeoff_label, + 'amount': self.payment_difference, + 'account_id': self.writeoff_account_id.id, + } + return payment_vals + + def _create_payment_vals_from_batch(self, batch_result): + batch_values = self._get_wizard_values_from_batch(batch_result) + return { + 'date': self.payment_date, + 'amount': batch_values['source_amount_currency'], + 'payment_type': batch_values['payment_type'], + 'partner_type': batch_values['partner_type'], + 'ref': self._get_batch_communication(batch_result), + 'journal_id': self.journal_id.id, + 'is_hr': True, + 'currency_id': batch_values['source_currency_id'], + 'partner_id': batch_values['partner_id'], + 'partner_bank_id': batch_result['key_values']['partner_bank_id'], + 'payment_method_id': self.payment_method_id.id, + 'destination_account_id': batch_result['lines'][0].account_id.id + } diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index 09d283eb..fbdf8dae 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -183,89 +183,94 @@ class AutomaticPurchase(models.Model): def create_po_by_vendor(self, vendor_id): current_time = datetime.now() - if not self.apo_type =='reordering': - name = "/PJ/" - else: - name = "/A/" + name = "/PJ/" if not self.apo_type == 'reordering' else "/A/" PRODUCT_PER_PO = 20 - auto_purchase_line = self.env['automatic.purchase.line'] - last_po = self.env['purchase.order'].search([ - ('partner_id', '=', vendor_id), - ('state', '=', 'done'), - ], order='id desc', limit=1) - - param_header = { - 'partner_id': vendor_id, - 'currency_id': 12, - 'user_id': self.env.user.id, - 'company_id': 1, # indoteknik dotcom gemilang - 'picking_type_id': 28, # indoteknik bandengan receipts - 'date_order': current_time, - 'from_apo': True, - 'note_description': 'Automatic PO' - } - + # Domain untuk semua baris dengan vendor_id tertentu domain = [ ('automatic_purchase_id', '=', self.id), ('partner_id', '=', vendor_id), ('qty_purchase', '>', 0) ] - products_len = auto_purchase_line.search_count(domain) - page = math.ceil(products_len / PRODUCT_PER_PO) - - # i start from zero (0) - for i in range(page): - new_po = self.env['purchase.order'].create([param_header]) - new_po.payment_term_id = new_po.partner_id.property_supplier_payment_term_id - new_po.name = new_po.name + name + str(i + 1) + # Tambahkan domain khusus untuk brand_id 22 dan 564 + special_brand_domain = domain + [('brand_id', 'in', [22, 564])] + regular_domain = domain + [('brand_id', 'not in', [22, 564])] + + # Fungsi untuk membuat PO berdasarkan domain tertentu + def create_po_for_domain(domain, special_payment_term=False): + products_len = auto_purchase_line.search_count(domain) + page = math.ceil(products_len / PRODUCT_PER_PO) + + for i in range(page): + # Buat PO baru + param_header = { + 'partner_id': vendor_id, + 'currency_id': 12, + 'user_id': self.env.user.id, + 'company_id': 1, # indoteknik dotcom gemilang + 'picking_type_id': 28, # indoteknik bandengan receipts + 'date_order': current_time, + 'from_apo': True, + 'note_description': 'Automatic PO' + } - self.env['automatic.purchase.match'].create([{ - 'automatic_purchase_id': self.id, - 'order_id': new_po.id - }]) + new_po = self.env['purchase.order'].create([param_header]) - lines = auto_purchase_line.search( - domain, - offset=i * PRODUCT_PER_PO, - limit=PRODUCT_PER_PO - ) + # Set payment_term_id khusus jika diperlukan + if special_payment_term: + new_po.payment_term_id = 29 + else: + new_po.payment_term_id = new_po.partner_id.property_supplier_payment_term_id - lines = auto_purchase_line.search( - domain, - offset=i * PRODUCT_PER_PO, - limit=PRODUCT_PER_PO - ) + new_po.name = new_po.name + name + str(i + 1) + self.env['automatic.purchase.match'].create([{ + 'automatic_purchase_id': self.id, + 'order_id': new_po.id + }]) + + # Ambil baris sesuai halaman + lines = auto_purchase_line.search( + domain, + offset=i * PRODUCT_PER_PO, + limit=PRODUCT_PER_PO + ) + + for line in lines: + product = line.product_id + sales_match = self.env['automatic.purchase.sales.match'].search([ + ('automatic_purchase_id', '=', self.id), + ('product_id', '=', product.id), + ]) + param_line = { + 'order_id': new_po.id, + 'product_id': product.id, + 'product_qty': line.qty_purchase, + 'qty_available_store': product.qty_available_bandengan, + 'suggest': product._get_po_suggest(line.qty_purchase), + 'product_uom_qty': line.qty_purchase, + 'price_unit': line.last_price, + 'ending_price': line.last_price, + 'taxes_id': [line.taxes_id.id] if line.taxes_id else None, + 'so_line_id': sales_match[0].sale_line_id.id if sales_match else None, + 'so_id': sales_match[0].sale_id.id if sales_match else None + } + new_po_line = self.env['purchase.order.line'].create([param_line]) + line.current_po_id = new_po.id + line.current_po_line_id = new_po_line.id + + self.create_purchase_order_sales_match(new_po) + + # Buat PO untuk special brand + if vendor_id == 23: + create_po_for_domain(special_brand_domain, special_payment_term=True) + + # Buat PO untuk regular domain + create_po_for_domain(regular_domain, "") - for line in lines: - product = line.product_id - sales_match = self.env['automatic.purchase.sales.match'].search([ - ('automatic_purchase_id', '=', self.id), - ('product_id', '=', product.id), - ]) - param_line = { - 'order_id': new_po.id, - 'product_id': product.id, - 'product_qty': line.qty_purchase, - 'qty_available_store': product.qty_available_bandengan, - 'suggest': product._get_po_suggest(line.qty_purchase), - 'product_uom_qty': line.qty_purchase, - 'price_unit': line.last_price, - 'ending_price': line.last_price, - 'taxes_id': [line.taxes_id.id] if line.taxes_id else None, - 'so_line_id': sales_match[0].sale_line_id.id if sales_match else None, - 'so_id': sales_match[0].sale_id.id if sales_match else None - } - new_po_line = self.env['purchase.order.line'].create([param_line]) - line.current_po_id = new_po.id - line.current_po_line_id = new_po_line.id - # self.update_purchase_price_so_line(line) - - self.create_purchase_order_sales_match(new_po) def update_purchase_price_so_line(self, apo): sales_match = self.env['automatic.purchase.sales.match'].search([ diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py index 6bbf9fde..e1b8f41f 100644 --- a/indoteknik_custom/models/barcoding_product.py +++ b/indoteknik_custom/models/barcoding_product.py @@ -12,6 +12,14 @@ 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')], string='Type', default='print') + barcode = fields.Char(string="Barcode") + + @api.constrains('barcode') + def _send_barcode_to_product(self): + for record in self: + if record.barcode and not record.product_id.barcode: + record.product_id.barcode = record.barcode @api.onchange('product_id', 'quantity') def _onchange_product_or_quantity(self): diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index 48f1c7f6..6920154a 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -153,6 +153,10 @@ class CustomerCommision(models.Model): bank_account = fields.Char(string='Account No', tracking=3) note_transfer = fields.Char(string='Keterangan') brand_ids = fields.Many2many('x_manufactures', string='Brands') + payment_status = fields.Selection([ + ('pending', 'Pending'), + ('payment', 'Payment'), + ], string='Payment Status', copy=False, readonly=True, tracking=3, default='pending') # add status for type of commision, fee, rebate / cashback # include child or not? @@ -228,6 +232,20 @@ class CustomerCommision(models.Model): raise UserError('Harus di approved oleh yang bersangkutan') return + def action_confirm_customer_payment(self): + if self.status != 'approved': + raise UserError('Commision harus di approve terlebih dahulu sebelum di konfirmasi pembayarannya') + + if self.payment_status == 'payment': + raise UserError('Customer Commision sudah berstatus Payment') + group_id = self.env.ref('indoteknik_custom.group_role_fat').id + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + if self.env.user.id not in users_in_group.mapped('id'): + raise UserError('Hanya bisa dikonfirmasi oleh FAT') + else: + self.payment_status = 'payment' + return + def generate_customer_commision(self): if self.commision_lines: raise UserError('Line sudah ada, tidak bisa di generate ulang') diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py index ae6dd2ae..b4bffbd2 100644 --- a/indoteknik_custom/models/coretax_fatur.py +++ b/indoteknik_custom/models/coretax_fatur.py @@ -59,6 +59,7 @@ class CoretaxFaktur(models.Model): ET.SubElement(tax_invoice, 'TrxCode').text = '04' ET.SubElement(tax_invoice, 'AddInfo') ET.SubElement(tax_invoice, 'CustomDoc') + ET.SubElement(tax_invoice, 'CustomDocMonthYear') ET.SubElement(tax_invoice, 'RefDesc').text = invoice.name ET.SubElement(tax_invoice, 'FacilityStamp') ET.SubElement(tax_invoice, 'SellerIDTKU').text = '0742260227086000000000' @@ -77,7 +78,7 @@ class CoretaxFaktur(models.Model): 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, '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' diff --git a/indoteknik_custom/models/invoice_reklas.py b/indoteknik_custom/models/invoice_reklas.py index 30da02d1..f5bb5a25 100644 --- a/indoteknik_custom/models/invoice_reklas.py +++ b/indoteknik_custom/models/invoice_reklas.py @@ -49,7 +49,7 @@ class InvoiceReklas(models.TransientModel): if self.reklas_type == 'penjualan': parameter_debit = { 'move_id': account_move.id, - 'account_id': 449, # uang muka penjualan + 'account_id': 668, # penerimaan belum alokasi 'partner_id': invoice.partner_id.id, 'currency_id': 12, 'debit': self.pay_amt, @@ -77,7 +77,7 @@ class InvoiceReklas(models.TransientModel): } parameter_credit = { 'move_id': account_move.id, - 'account_id': 401, + 'account_id': 669, 'partner_id': invoice.partner_id.id, 'currency_id': 12, 'debit': 0, diff --git a/indoteknik_custom/models/invoice_reklas_penjualan.py b/indoteknik_custom/models/invoice_reklas_penjualan.py index 5027c8af..80c3ed43 100644 --- a/indoteknik_custom/models/invoice_reklas_penjualan.py +++ b/indoteknik_custom/models/invoice_reklas_penjualan.py @@ -33,7 +33,7 @@ class InvoiceReklasPenjualan(models.TransientModel): parameter_debit = { 'move_id': account_move.id, - 'account_id': 449, # uang muka penjualan + 'account_id': 668, # uang muka penjualan 'partner_id': invoice.partner_id.id, 'currency_id': 12, 'debit': invoice.pay_amt, diff --git a/indoteknik_custom/models/ir_actions_report.py b/indoteknik_custom/models/ir_actions_report.py new file mode 100644 index 00000000..83636945 --- /dev/null +++ b/indoteknik_custom/models/ir_actions_report.py @@ -0,0 +1,51 @@ +from odoo import models +from odoo.http import request +import requests +class IrActionsReport(models.Model): + _inherit = 'ir.actions.report' + + def _get_readable_fields(self): + if self.env.context.get('active_model') == 'sale.order': + self.send_to_telegram() + return super()._get_readable_fields() + + def send_to_telegram(self): + so_id = self.env.context.get('active_id') + if so_id: + sale_order = self.env['sale.order'].browse(so_id) + if sale_order.amount_total < 50000000: + return + # ci_vita 7751529082:AAE9XsZa_Pj2Pi2IN1grX98WkwTaIt32pbI & 5081411103 + # iman 7094158106:AAHpWtYOMnA3Yqm_Fvrr3Vw7MrB45vLV9AY & 6592318498 + bot_name_iman = '7094158106:AAHpWtYOMnA3Yqm_Fvrr3Vw7MrB45vLV9AY' + chat_id_iman = '-1002493002821' + bot_name_vita = '7751529082:AAE9XsZa_Pj2Pi2IN1grX98WkwTaIt32pbI' + chat_id_vita = '5081411103' + apiURL = f'https://api.telegram.org/bot{bot_name_iman}/sendMessage' + try: + requests.post(apiURL, json={'chat_id': chat_id_iman, 'text': sale_order.name + " senilai Rp" + self.format_currency(sale_order.amount_total) + ' untuk customer ' + sale_order.partner_id.name + ' telah dibuat oleh sales ' +sale_order.user_id.name}) + except Exception as e: + print(e) + + # id ci vita 79160 + # id iman 112718 + # partner = request.env['res.partner'].search([('id', '=', 112718)], limit=1) + # telegram_data = { + # 'tittle': sale_order.name, + # 'about': sale_order.name, + # 'user_id': partner, + # 'id_data': sale_order.id, + # 'username': '@' + sale_order.name.replace('/', '') + # } + # channel_data = self.env['website.telegram'].search([('tittle', '=', sale_order.name)]) + # if channel_data: + # channel_data.send_to_telegram(sale_order.name + " Telah di print Oleh " + self.env.user.name) + # for pick in self: + # self._check_telegram(pick) + # else: + # telegram = self.env['website.telegram'].create(telegram_data) + # telegram.create_channel(sale_order.name + " Telah di print Oleh " + self.env.user.name) + + def format_currency(self, number): + number = int(number) + return "{:,}".format(number).replace(',', '.')
\ No newline at end of file diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 29608297..efacb95f 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -67,6 +67,25 @@ class ProductTemplate(models.Model): print_barcode = fields.Boolean(string='Print Barcode', default=True) # qr_code = fields.Binary("QR Code", compute='_compute_qr_code') + @api.model + def create(self, vals): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + active_model = self.env.context.get('active_model') + if self.env.user.id not in users_in_group.mapped('id') and active_model == None: + raise UserError('Hanya MD yang bisa membuat Product') + result = super(ProductTemplate, self).create(vals) + return result + + # def write(self, values): + # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + # users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + # active_model = self.env.context.get('active_model') + # if self.env.user.id not in users_in_group.mapped('id') and active_model == None: + # raise UserError('Hanya MD yang bisa mengedit Product') + # result = super(ProductTemplate, self).write(values) + # return result + # def _compute_qr_code(self): # for rec in self.product_variant_ids: # qr = qrcode.QRCode( @@ -403,6 +422,25 @@ class ProductProduct(models.Model): merchandise_ok = fields.Boolean(string='Product Promotion') qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant') + @api.model + def create(self, vals): + group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + active_model = self.env.context.get('active_model') + users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + if self.env.user.id not in users_in_group.mapped('id') and active_model == None: + raise UserError('Hanya MD yang bisa membuat Product') + result = super(ProductProduct, self).create(vals) + return result + + # def write(self, values): + # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id + # active_model = self.env.context.get('active_model') + # users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) + # if self.env.user.id not in users_in_group.mapped('id') and active_model == None: + # raise UserError('Hanya MD yang bisa mengedit Product') + # result = super(ProductProduct, self).write(values) + # return result + def _compute_qr_code_variant(self): for rec in self: # Skip inactive variants diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 777e8ed2..d90c4a8a 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -86,6 +86,7 @@ class PurchaseOrder(models.Model): total_cost_service = fields.Float(string='Total Cost Service') total_delivery_amt = fields.Float(string='Total Delivery Amt') store_name = fields.Char(string='Nama Toko') + purchase_order_count = fields.Integer('Purchase Order Count', related='partner_id.purchase_order_count') @api.onchange('total_cost_service') def _onchange_total_cost_service(self): @@ -154,6 +155,7 @@ class PurchaseOrder(models.Model): 'invoice_date': current_date, 'date': current_date, 'invoice_origin': self.name, + 'purchase_order_id': self.id, 'move_type': 'in_invoice' } @@ -178,7 +180,7 @@ class PurchaseOrder(models.Model): 'move_id': bills.id, 'product_id': product_dp.id, # product down payment 'name': '[IT.121456] Down Payment', # product down payment - 'account_id': 401, # Uang Muka persediaan barang dagang + 'account_id': 669, # Uang Muka persediaan barang dagang # 'price_unit': move_line.price_unit, 'quantity': -1, 'product_uom_id': 1, @@ -230,6 +232,7 @@ class PurchaseOrder(models.Model): 'invoice_date': current_date, 'date': current_date, 'invoice_origin': self.name, + 'purchase_order_id': self.id, 'move_type': 'in_invoice' } @@ -240,7 +243,7 @@ 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 + 'account_id': 669, # Uang Muka persediaan barang dagang 'quantity': 1, 'product_uom_id': 1, 'tax_ids': [line[0].taxes_id.id for line in self.order_line], @@ -306,6 +309,7 @@ class PurchaseOrder(models.Model): invoice_vals = { 'ref': self.partner_ref or '', 'move_type': move_type, + 'purchase_order_id': self.id, 'narration': self.notes, 'currency_id': self.currency_id.id, 'invoice_user_id': self.user_id and self.user_id.id or self.env.user.id, @@ -387,6 +391,13 @@ class PurchaseOrder(models.Model): } return action + def open_form_multi_ask_approval_po(self): + action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_purchase_order_multi_ask_approval') + action['context'] = { + 'po_ids': [x.id for x in self] + } + return action + def open_form_multi_create_uang_muka(self): action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_purchase_order_multi_uangmuka') action['context'] = { @@ -414,6 +425,13 @@ class PurchaseOrder(models.Model): purchase.button_confirm() + def action_multi_ask_approval_po(self): + for purchase in self: + if purchase.state != 'draft': + continue + + purchase.po_approve() + def open_form_multi_update_paid_status(self): action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_purchase_order_multi_update') action['context'] = { @@ -965,7 +983,14 @@ class PurchaseOrder(models.Model): sales_price -= (sale_order_line.fee_third_party_line / sale_order_line.product_uom_qty) * line.qty_po sum_sales_price += sales_price - purchase_price = po_line.price_subtotal / po_line.product_qty * line.qty_po + + purchase_price = po_line.price_subtotal + if po_line.ending_price > 0: + if po_line.taxes_id.id == 22: + ending_price = po_line.ending_price / 1.11 + purchase_price = ending_price + else: + purchase_price = po_line.ending_price if line.purchase_order_id.delivery_amount > 0: purchase_price += (po_line.delivery_amt_line / po_line.product_qty) * line.qty_po if line.purchase_order_id.delivery_amt > 0: diff --git a/indoteknik_custom/models/purchase_order_line.py b/indoteknik_custom/models/purchase_order_line.py index 587a09a1..033469b8 100755 --- a/indoteknik_custom/models/purchase_order_line.py +++ b/indoteknik_custom/models/purchase_order_line.py @@ -321,32 +321,60 @@ class PurchaseOrderLine(models.Model): def compute_item_margin(self): sum_so_margin = sum_sales_price = sum_margin = 0 + for line in self: - if not line.product_id or line.product_id.type == 'service' or not self.order_id.sale_order_id: + product = line.product_id + order = line.order_id + + # Skip jika tidak ada product_id, produk adalah service, atau tidak ada purchase order terkait + if not product or product.type == 'service' or not order: line.so_item_margin = 0 line.so_item_percent_margin = 0 line.item_margin = 0 line.item_percent_margin = 0 continue - sale_order_line = self.env['sale.order.line'].search( - [('product_id', '=', line.product_id.id), - ('order_id', '=', line.order_id.sale_order_id.id)], limit=1, order='price_reduce_taxexcl') - line.so_item_margin = sale_order_line.item_margin - line.so_item_percent_margin = sale_order_line.item_percent_margin - sum_so_margin += sale_order_line.item_margin - sales_price = sale_order_line.price_reduce_taxexcl * sale_order_line.product_uom_qty - if sale_order_line.order_id.shipping_cost_covered == 'indoteknik': - sales_price -= sale_order_line.delivery_amt_line - if sale_order_line.order_id.fee_third_party > 0: - sales_price -= sale_order_line.fee_third_party_line - sum_sales_price += sales_price + + # Cari semua sale.order.line terkait dengan purchase.order melalui tabel purchase.order.sales.match + sales_matches = self.env['purchase.order.sales.match'].search([ + ('purchase_order_id', '=', order.id), + ('product_id', '=', product.id) + ]) + + total_sales_price = total_margin = total_qty_so = 0 + for match in sales_matches: + sale_order_line = match.sale_line_id + + # Hitung harga jual setelah mempertimbangkan biaya tambahan + sales_price = sale_order_line.price_reduce_taxexcl * match.qty_so + if sale_order_line.order_id.shipping_cost_covered == 'indoteknik': + sales_price -= sale_order_line.delivery_amt_line + if sale_order_line.order_id.fee_third_party > 0: + sales_price -= sale_order_line.fee_third_party_line + + total_sales_price += sales_price + total_margin += sale_order_line.item_margin + total_qty_so += match.qty_so + + # Set margin berdasarkan total dari semua sales order yang terkait + line.so_item_margin = total_margin + line.so_item_percent_margin = (total_margin / total_sales_price) * 100 if total_sales_price else 0 + + sum_so_margin += total_margin + sum_sales_price += total_sales_price + + # Hitung harga pembelian dengan mempertimbangkan biaya pengiriman purchase_price = line.price_subtotal - if line.order_id.delivery_amount > 0: + if order.delivery_amount > 0: purchase_price += line.delivery_amt_line - real_item_margin = sales_price - purchase_price - real_item_percent_margin = round((real_item_margin/sales_price), 2) * 100 + + # Hitung margin dan persentase margin + real_item_margin = total_sales_price - purchase_price + real_item_percent_margin = (real_item_margin / total_sales_price) * 100 if total_sales_price else 0 + + # Set nilai margin ke dalam line line.item_margin = real_item_margin line.item_percent_margin = real_item_percent_margin + sum_margin += real_item_margin def compute_delivery_amt_line(self): diff --git a/indoteknik_custom/models/purchase_order_multi_ask_approval.py b/indoteknik_custom/models/purchase_order_multi_ask_approval.py new file mode 100644 index 00000000..69f8e5a8 --- /dev/null +++ b/indoteknik_custom/models/purchase_order_multi_ask_approval.py @@ -0,0 +1,22 @@ +from odoo import models, fields +import logging + +_logger = logging.getLogger(__name__) + + +class PurchaseOrderMultiUpdate(models.TransientModel): + _name = 'purchase.order.multi_ask_approval' + + def save_multi_ask_approval_po(self): + po_ids = self._context['po_ids'] + purchase = self.env['purchase.order'].browse(po_ids) + purchase.action_multi_ask_approval_po() + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Notification', + 'message': 'PO berhasil di ask_approval', + 'next': {'type': 'ir.actions.act_window_close'}, + } + }
\ No newline at end of file diff --git a/indoteknik_custom/models/purchase_order_multi_uangmuka.py b/indoteknik_custom/models/purchase_order_multi_uangmuka.py index dd63e698..0570efd9 100644 --- a/indoteknik_custom/models/purchase_order_multi_uangmuka.py +++ b/indoteknik_custom/models/purchase_order_multi_uangmuka.py @@ -76,7 +76,7 @@ class PurchaseOrderMultiUangmuka(models.TransientModel): param_debit = { 'move_id': account_move.id, - 'account_id': 401, # uang muka persediaan barang dagang + 'account_id': 669, # uang muka persediaan barang dagang 'partner_id': partner_id, 'currency_id': 12, 'debit': order.amount_total, diff --git a/indoteknik_custom/models/purchase_order_sales_match.py b/indoteknik_custom/models/purchase_order_sales_match.py index d1d929d3..ed013dd5 100644 --- a/indoteknik_custom/models/purchase_order_sales_match.py +++ b/indoteknik_custom/models/purchase_order_sales_match.py @@ -24,6 +24,18 @@ class PurchaseOrderSalesMatch(models.Model): margin_item = fields.Float(string='Margin') delivery_amt = fields.Float(string='Delivery Amount', compute='_compute_delivery_amt') margin_deduct = fields.Float(string='After Deduct', compute='_compute_delivery_amt') + 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') + + def _compute_purchase_line_id(self): + for line in self: + line.purchase_line_id = self.env['purchase.order.line'].search([('order_id', '=', line.purchase_order_id.id), ('product_id', '=', line.product_id.id)]) + + def _compute_purchase_price_po(self): + for line in self: + purchase_price = self.env['purchase.order.line'].search([('order_id', '=', line.purchase_order_id.id), ('product_id', '=', line.product_id.id)]) + line.purchase_price_po = purchase_price.price_unit def _compute_delivery_amt(self): for line in self: diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index 4efb0cd4..902bc34b 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -183,6 +183,7 @@ class OutstandingSales(models.Model): join product_template pt on pt.id = pp.product_tmpl_id left join x_manufactures xm on xm.id = pt.x_manufacture where sp.state in ('draft', 'waiting', 'confirmed', 'assigned') + and sm.state in ('draft', 'waiting', 'confirmed', 'partially_available') and sp.name like '%OUT%' ) """) diff --git a/indoteknik_custom/models/report_logbook_sj.py b/indoteknik_custom/models/report_logbook_sj.py index a1b6299c..17119c12 100644 --- a/indoteknik_custom/models/report_logbook_sj.py +++ b/indoteknik_custom/models/report_logbook_sj.py @@ -29,6 +29,8 @@ class ReportLogbookSJ(models.Model): string='Status', tracking=True, ) + + sj_number = fields.Char(string='Picking', related='report_logbook_sj_line.name') count_line = fields.Char(string='Count Line', compute='_compute_count_line') diff --git a/indoteknik_custom/models/report_stock_forecasted.py b/indoteknik_custom/models/report_stock_forecasted.py index c9d54a15..37082869 100644 --- a/indoteknik_custom/models/report_stock_forecasted.py +++ b/indoteknik_custom/models/report_stock_forecasted.py @@ -1,51 +1,4 @@ from odoo import api, models class ReplenishmentReport(models.AbstractModel): - _inherit = 'report.stock.report_product_product_replenishment' - - @api.model - def _get_report_lines(self, product_template_ids, product_variant_ids, wh_location_ids): - lines = super(ReplenishmentReport, self)._get_report_lines(product_template_ids, product_variant_ids, wh_location_ids) - # result_dict = {} - # - # for line in lines: - # document_out = line.get('document_out') - # - # if document_out and "SO/" in document_out.name: - # order_id = document_out.id - # if document_out == False: - # continue - # product_id = line.get('product', {}).get('id') - # query = [('product_id', '=', product_id)] - # - # if order_id: - # result = self._calculate_result(line) - # quantity = line.get('quantity', 0) - # result_dict.setdefault(order_id, []).append((result, quantity)) - # - # for order_id, results in result_dict.items(): - # sales_order = self.env['sale.order'].browse(order_id) - # - # for result, quantity in results: - # self.env['sales.order.fullfillment'].create({ - # 'sales_order_id': sales_order.id, - # 'product_id': product_id, - # 'reserved_from': result, - # 'qty_fullfillment': quantity, - # }) - return lines - - def _calculate_result(self, line): - if line['document_in']: - return str(line["document_in"].name) - elif line['reservation'] and not line['document_in']: - return 'Reserved from stock' - elif line['replenishment_filled']: - if line['document_out']: - return 'Inventory On Hand' - else: - return 'Free Stock' - else: - return 'Unfulfilled' - - + _inherit = 'report.stock.report_product_product_replenishment'
\ No newline at end of file diff --git a/indoteknik_custom/models/requisition.py b/indoteknik_custom/models/requisition.py index c972b485..1d350929 100644 --- a/indoteknik_custom/models/requisition.py +++ b/indoteknik_custom/models/requisition.py @@ -276,10 +276,11 @@ class RequisitionLine(models.Model): _name = 'requisition.line' _description = 'Requisition Line' _order = 'requisition_id, id' + _inherit = ['mail.thread'] requisition_id = fields.Many2one('requisition', string='Ref', required=True, ondelete='cascade', index=True, copy=False) brand_id = fields.Many2one('x_manufactures', string='Brand') - product_id = fields.Many2one('product.product', string='Product') + product_id = fields.Many2one('product.product', string='Product', tracking=3,) partner_id = fields.Many2one('res.partner', string='Vendor') qty_purchase = fields.Float(string='Qty Purchase') price_unit = fields.Float(string='Price') @@ -331,6 +332,45 @@ class RequisitionLine(models.Model): line.taxes_id = taxes line.partner_id = purchase_pricelist.vendor_id.id + @api.model + def create(self, vals): + record = super(RequisitionLine, self).create(vals) + record._track_changes('Tambah') + return record + + def write(self, vals): + for record in self: + old_values = {field: record[field] for field in vals if field in record} + + result = super(RequisitionLine, self).write(vals) + + for record in self: + record._track_changes('Updated', old_values) + + return result + + def unlink(self): + for record in self: + record._track_changes('Hapus') + return super(RequisitionLine, self).unlink() + + def _track_changes(self, action, old_values=None): + message = f"Produk telah di-{action} : <br/>" + if action == 'Tambah': + # message += f"<br/> Product: {self.product_id.name}" + message += f"Product: {self.product_id.name} <br/> Vendor: {self.partner_id.name} <br/> Qty: {self.qty_purchase} <br/> Price: {self.price_unit} <br/> Tax: {self.tax_id.name} <br/> Subtotal: {self.subtotal} <br/> Brand: {self.brand_id.x_name}" + elif action == 'Hapus': + # message += f"<br/> Deleted Product: {self.product_id.name}" + message += f"<br/> Deleted Product: {self.product_id.name} <br/> Vendor: {self.partner_id.name} Qty: {self.qty_purchase} <br/> Price: {self.price_unit} <br/> Tax: {self.tax_id.name} <br/> Subtotal: {self.subtotal} <br/> Brand: {self.brand_id.x_name}" + else: # Updated + for field, old_value in old_values.items(): + new_value = self[field] + if old_value != new_value: + field_label = self._fields[field].string # Ambil nama label field + message += f"{field_label}: {old_value} -> {new_value}<br/>" + + if self.requisition_id: + self.requisition_id.message_post(body=message) class RequisitionPurchaseMatch(models.Model): _name = 'requisition.purchase.match' diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index f081e274..7e574a72 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -147,6 +147,7 @@ class ResPartner(models.Model): "customer is crossed blocking amount." "Set its value to 0.00 to disable " "this feature", tracking=3) + telegram_id = fields.Char(string="Telegram") @api.model def _default_payment_term(self): @@ -182,6 +183,19 @@ class ResPartner(models.Model): # # raise UserError('You name it') # return res + + @api.constrains('name') + def _check_duplicate_name(self): + for record in self: + if record.name: + # Mencari partner lain yang memiliki nama sama (case-insensitive) + existing_partner = self.env['res.partner'].search([ + ('id', '!=', record.id), # Hindari mencocokkan diri sendiri + ('name', '=', record.name) # Case-insensitive search + ], limit=1) + + if existing_partner: + raise ValidationError(f"Nama '{record.name}' sudah digunakan oleh partner lain!") def write(self, vals): # Fungsi rekursif untuk meng-update semua child, termasuk child dari child diff --git a/indoteknik_custom/models/res_users.py b/indoteknik_custom/models/res_users.py index 31b84ae3..b0864f2c 100755 --- a/indoteknik_custom/models/res_users.py +++ b/indoteknik_custom/models/res_users.py @@ -70,6 +70,10 @@ class ResUsers(models.Model): vouchers = self.env['voucher'].get_active_voucher([('show_on_email', '=', 'user_activation')]) if not vouchers: return None return ', '.join(x.code for x in vouchers) + if type == 'switch_account': + vouchers = self.env['voucher'].get_active_voucher([('excl_pricelist_ids', 'not in', [1]), ('apply_type', 'in', ['all', 'brand']), ('account_type', 'in', ['all', 'company']), ('visibility', 'in', ['public'])]) + if not vouchers: return None + return ', '.join(x.code for x in vouchers) return None def check_access(self, model, mode): diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 88c32fb6..3534306c 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -7,6 +7,50 @@ from collections import defaultdict _logger = logging.getLogger(__name__) +class CancelReasonOrder(models.TransientModel): + _name = 'cancel.reason.order' + _description = 'Wizard for Cancel Reason order' + + request_id = fields.Many2one('sale.order', string='Request') + reason_cancel = fields.Selection([ + ('harga_terlalu_mahal', 'Harga barang terlalu mahal'), + ('harga_web_tidak_valid', 'Harga web tidak valid'), + ('stok_kosong', 'Stock kosong'), + ('tidak_mau_indent', 'Customer tidak mau indent'), + ('batal_rencana_pembelian', 'Customer membatalkan rencana pembelian'), + ('vendor_tidak_support_demo', 'Vendor tidak support demo/trial product'), + ('product_knowledge_kurang', 'Product knowledge kurang baik'), + ('barang_tidak_sesuai', 'Barang tidak sesuai/tepat'), + ('tidak_sepakat_pembayaran', 'Tidak menemukan kesepakatan untuk pembayaran'), + ('dokumen_tidak_support', 'Indoteknik tidak bisa support document yang dibutuhkan (Ex: TKDN, COO, SNI)'), + ('ganti_quotation', 'Ganti Quotation'), + ('testing_internal', 'Testing Internal'), + ('revisi_data', 'Revisi Data'), + ], string='Reason for Cancel', required=True, copy=False, index=True, tracking=3) + attachment_bukti = fields.Many2many( + 'ir.attachment', + string="Attachment Bukti", readonly=False, + tracking=3, required=True + ) + nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3) + + def confirm_reject(self): + order = self.request_id + if order: + order.write({'reason_cancel': self.reason_cancel}) + if not self.attachment_bukti: + 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]) + if self.reason_cancel == 'ganti_quotation': + if self.nomor_so_pengganti: + order.write({'nomor_so_pengganti': self.nomor_so_pengganti}) + else: + raise UserError('Nomor SO pengganti wajib disertakan') + order.confirm_cancel_order() + + return {'type': 'ir.actions.act_window_close'} class SaleOrder(models.Model): _inherit = "sale.order" @@ -29,11 +73,11 @@ class SaleOrder(models.Model): shipping_cost_covered = fields.Selection([ ('indoteknik', 'Indoteknik'), ('customer', 'Customer') - ], string='Shipping Covered by', help='Siapa yang menanggung biaya ekspedisi?', copy=False) + ], string='Shipping Covered by', help='Siapa yang menanggung biaya ekspedisi?', copy=False, tracking=3) shipping_paid_by = fields.Selection([ ('indoteknik', 'Indoteknik'), ('customer', 'Customer') - ], string='Shipping Paid by', help='Siapa yang talangin dulu Biaya ekspedisi-nya?', copy=False) + ], 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)]) have_outstanding_invoice = fields.Boolean('Have Outstanding Invoice', compute='_have_outstanding_invoice') have_outstanding_picking = fields.Boolean('Have Outstanding Picking', compute='_have_outstanding_picking') @@ -145,6 +189,34 @@ class SaleOrder(models.Model): ('PNR', 'Pareto Non Repeating'), ('NP', 'Non Pareto') ]) + 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'), + ('stok_kosong', 'Stock kosong'), + ('tidak_mau_indent', 'Customer tidak mau indent'), + ('batal_rencana_pembelian', 'Customer membatalkan rencana pembelian'), + ('vendor_tidak_support_demo', 'Vendor tidak support demo/trial product'), + ('product_knowledge_kurang', 'Product knowledge kurang baik'), + ('barang_tidak_sesuai', 'Barang tidak sesuai/tepat'), + ('tidak_sepakat_pembayaran', 'Tidak menemukan kesepakatan untuk pembayaran'), + ('dokumen_tidak_support', 'Indoteknik tidak bisa support document yang dibutuhkan (Ex: TKDN, COO, SNI)'), + ('ganti_quotation', 'Ganti Quotation'), + ('testing_internal', 'Testing Internal'), + ], string='Reason for Cancel', copy=False, index=True, tracking=3) + attachment_bukti = fields.Many2one( + 'ir.attachment', + string="Attachment Bukti Cancel", readonly=False, + ) + nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3) + + def _compute_shipping_method_picking(self): + for order in self: + if order.picking_ids: + carrier_names = order.picking_ids.mapped('carrier_id.name') + order.shipping_method_picking = ', '.join(filter(None, carrier_names)) + else: + order.shipping_method_picking = False @api.onchange('payment_status') def _is_continue_transaction(self): @@ -541,22 +613,22 @@ class SaleOrder(models.Model): redirect_url = json.loads(lookup_json)['redirect_url'] self.payment_link_midtrans = str(redirect_url) - # Generate QR code - qr = qrcode.QRCode( - version=1, - error_correction=qrcode.constants.ERROR_CORRECT_L, - box_size=10, - border=4, - ) - qr.add_data(redirect_url) - qr.make(fit=True) - img = qr.make_image(fill_color="black", back_color="white") + if 'redirect_url' in response: + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=10, + border=4, + ) + qr.add_data(redirect_url) + qr.make(fit=True) + img = qr.make_image(fill_color="black", back_color="white") - buffer = BytesIO() - img.save(buffer, format="PNG") - qr_code_img = base64.b64encode(buffer.getvalue()).decode() + buffer = BytesIO() + img.save(buffer, format="PNG") + qr_code_img = base64.b64encode(buffer.getvalue()).decode() - self.payment_qr_code = qr_code_img + self.payment_qr_code = qr_code_img @api.model def _generate_so_access_token(self, limit=50): @@ -760,6 +832,7 @@ class SaleOrder(models.Model): def sale_order_approve(self): self.check_credit_limit() + self.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') self.check_due() @@ -817,6 +890,8 @@ class SaleOrder(models.Model): order.approval_status = 'pengajuan2' return self._create_approval_notification('Pimpinan') elif order._requires_approval_margin_manager(): + self.check_credit_limit() + self.check_limit_so_to_invoice() order.approval_status = 'pengajuan1' return self._create_approval_notification('Sales Manager') @@ -923,6 +998,29 @@ class SaleOrder(models.Model): 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: + # Ambil jumlah outstanding_amount dan rec.amount_total sebagai current_total + outstanding_amount = rec.outstanding_amount + current_total = rec.amount_total + outstanding_amount + + # Ambil blocking stage dari partner + block_stage = rec.partner_id.parent_id.blocking_stage if rec.partner_id.parent_id else rec.partner_id.blocking_stage or 0 + is_cbd = rec.partner_id.parent_id.property_payment_term_id.id == 26 if rec.partner_id.parent_id else rec.partner_id.property_payment_term_id.id == 26 or False + + # Ambil jumlah nilai dari SO yang invoice_status masih 'to invoice' + so_to_invoice = 0 + for sale in rec.partner_id.sale_order_ids: + if sale.invoice_status == 'to invoice': + so_to_invoice = so_to_invoice + sale.amount_total + # Hitung remaining credit limit + remaining_credit_limit = block_stage - current_total - so_to_invoice + + # 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.") + % (rec.partner_id.name, block_stage - current_total, block_stage, outstanding_amount)) + def validate_different_vendor(self): if self.vendor_approval_id.filtered(lambda v: v.state == 'draft'): draft_names = ", ".join(self.vendor_approval_id.filtered(lambda v: v.state == 'draft').mapped('number')) @@ -971,6 +1069,8 @@ class SaleOrder(models.Model): def action_confirm(self): for order in self: + order.check_credit_limit() + 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') @@ -1032,9 +1132,14 @@ class SaleOrder(models.Model): if self.have_outstanding_invoice: raise UserError("Invoice harus di Cancel dahulu") + + disallow_states = ['draft', 'waiting', 'confirmed', 'assigned'] + for picking in self.picking_ids: + if picking.state in disallow_states: + raise UserError("DO yang draft, waiting, confirmed, atau assigned harus di-cancel oleh Logistik") for line in self.order_line: if line.qty_delivered > 0: - raise UserError("DO harus di-cancel terlebih dahulu.") + raise UserError("DO yang done harus di-Return oleh Logistik") if not self.web_approval: self.web_approval = 'company' @@ -1045,8 +1150,24 @@ class SaleOrder(models.Model): self.due_id = False if main_parent.use_so_approval: self.send_notif_to_salesperson(cancel=True) + for order in self: + if order.amount_total > 30000000: + return { + 'type': 'ir.actions.act_window', + 'name': _('Cancel Reason'), + 'res_model': 'cancel.reason.order', + 'view_mode': 'form', + 'target': 'new', + 'context': {'default_request_id': self.id}, + } return super(SaleOrder, self).action_cancel() - + + def confirm_cancel_order(self): + """Fungsi ini akan dipanggil oleh wizard setelah alasan pembatalan dipilih""" + if self.state != 'cancel': + self.state = 'cancel' + return super(SaleOrder, self).action_cancel() + 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 @@ -1078,10 +1199,10 @@ class SaleOrder(models.Model): return False def _requires_approval_margin_leader(self): - return self.total_percent_margin <= 13 and not self.env.user.is_leader + return self.total_percent_margin < 15 and not self.env.user.is_leader def _requires_approval_margin_manager(self): - return self.total_percent_margin <= 20 and not self.env.user.is_leader and not self.env.user.is_sales_manager + 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' diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 29a046fa..aed95aab 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -38,11 +38,11 @@ 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') + desc_updatable = fields.Boolean(string='desc boolean', default=True, compute='_get_desc_updatable') def _get_desc_updatable(self): for line in self: - if line.product_id.id != 417724: + if line.product_id.id != 417724 and line.product_id.id: line.desc_updatable = False else: line.desc_updatable = True @@ -280,7 +280,7 @@ 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: + if line.product_id.id != 417724 and line.product_id.id: line.desc_updatable = False else: line.desc_updatable = True diff --git a/indoteknik_custom/models/sale_order_multi_uangmuka_penjualan.py b/indoteknik_custom/models/sale_order_multi_uangmuka_penjualan.py index f9120290..96c2f676 100644 --- a/indoteknik_custom/models/sale_order_multi_uangmuka_penjualan.py +++ b/indoteknik_custom/models/sale_order_multi_uangmuka_penjualan.py @@ -68,7 +68,7 @@ class PurchaseOrderMultiUangmukaPenjualan(models.TransientModel): param_credit = { 'move_id': account_move.id, - 'account_id': 449, # uang muka penjualan + 'account_id': 668, # penerimaan belum alokasi 'partner_id': partner_id, 'currency_id': 12, 'debit': 0, diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 87e8370f..8afff6e3 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -76,8 +76,11 @@ class ProductTemplate(models.Model): ('product_id', 'in', template.product_variant_ids.ids), ('location_id', 'in', target_locations), ]) - - is_in_bu = any(quant.available_quantity > 0 for quant in stock_quant) + is_in_bu = False + for quant in stock_quant: + if quant.product_id.qty_free_bandengan > 0: + is_in_bu = True + break cleaned_desc = BeautifulSoup(template.website_description or '', "html.parser").get_text() website_description = template.website_description if cleaned_desc else '' diff --git a/indoteknik_custom/models/stock_immediate_transfer.py b/indoteknik_custom/models/stock_immediate_transfer.py index 35c17192..c2a293f9 100644 --- a/indoteknik_custom/models/stock_immediate_transfer.py +++ b/indoteknik_custom/models/stock_immediate_transfer.py @@ -19,6 +19,7 @@ class StockImmediateTransfer(models.TransientModel): for picking in pickings_to_do: # picking.send_mail_bills() # picking.send_koli_to_so() + # If still in draft => confirm and assign if picking.state == 'draft': picking.action_confirm() if picking.state != 'assigned': diff --git a/indoteknik_custom/models/stock_inventory.py b/indoteknik_custom/models/stock_inventory.py new file mode 100644 index 00000000..12a891de --- /dev/null +++ b/indoteknik_custom/models/stock_inventory.py @@ -0,0 +1,59 @@ +from odoo import models, api, fields +from odoo.exceptions import UserError +from datetime import datetime +import logging + +_logger = logging.getLogger(__name__) + + +class StockInventory(models.Model): + _inherit = ['stock.inventory'] + _order = 'id desc' + _rec_name = 'number' + + number = fields.Char(string='Document No', index=True, copy=False, readonly=True) + adjusment_type = fields.Selection([ + ('in', 'Adjusment In'), + ('out', 'Adjusment Out'), + ], string='Adjusments Type', required=True) + + def _generate_number_stock_inventory(self): + """Men-generate nomor untuk semua stock inventory yang belum memiliki number.""" + stock_records = self.env['stock.inventory'].search([('number', '=', False)], order='id asc') + for record in stock_records: + self._assign_number(record) + + _logger.info('Generate Number Done') + + def _assign_number(self, record): + """Menentukan nomor berdasarkan kategori Adjust-In atau Adjust-Out.""" + name_upper = record.name.upper() if record.name else "" + + if self.adjusment_type == 'out' or "ADJUST OUT" in name_upper or "ADJUST-OUT" in name_upper or "OUT" in name_upper: + last_number = self._get_last_sequence("ADJUST/OUT/") + record.number = f"ADJUST/OUT/{last_number}" + elif self.adjusment_type == 'in' or "ADJUST IN" in name_upper or "ADJUST-IN" in name_upper or "IN" in name_upper: + last_number = self._get_last_sequence("ADJUST/IN/") + record.number = f"ADJUST/IN/{last_number}" + else: + record.number = "UNKNOWN" # Jika tidak termasuk kategori + + def _get_last_sequence(self, prefix): + """Mengambil nomor terakhir berdasarkan prefix (ADJUST/OUT/ atau ADJUST/IN/) dengan format 00001, 00002, dst.""" + last_record = self.env['stock.inventory'].search( + [("number", "like", f"{prefix}%")], order="number desc", limit=1 + ) + + if last_record and last_record.number: + try: + last_number = int(last_record.number.split("/")[-1]) # Ambil angka terakhir + return str(last_number + 1).zfill(5) # Format jadi 00001, 00002, dst. + except ValueError: + return "00001" # Jika format tidak valid, mulai dari 00001 + return "00001" # Jika belum ada data, mulai dari 00001 + + @api.model + def create(self, vals): + order = super(StockInventory, self).create(vals) + self._assign_number(order) + return order diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index df91d451..0699295f 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -97,7 +97,7 @@ class StockPicking(models.Model): purchase_representative_id = fields.Many2one('res.users', related='move_lines.purchase_line_id.order_id.user_id', string="Purchase Representative") carrier_id = fields.Many2one('delivery.carrier', string='Shipping Method') shipping_status = fields.Char(string='Shipping Status', compute="_compute_shipping_status") - date_reserved = fields.Datetime(string="Date Reserved", help='Tanggal ter-reserved semua barang nya') + date_reserved = fields.Datetime(string="Date Reserved", help='Tanggal ter-reserved semua barang nya', copy=False) status_printed = fields.Selection([ ('not_printed', 'Belum Print'), ('printed', 'Printed') @@ -123,7 +123,7 @@ class StockPicking(models.Model): ('ready', 'Ready to Ship'), ('done', 'Done'), ('cancel', 'Cancelled'), - ], string='Status Reserve', readonly=True, tracking=True, help="The current state of the stock picking.") + ], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.") notee = fields.Text(string="Note") quantity_koli = fields.Float(string="Quantity Koli", copy=False) @@ -523,18 +523,52 @@ class StockPicking(models.Model): def check_state_reserve(self): pickings = self.search([ ('state', 'not in', ['cancel', 'draft', 'done']), + ('picking_type_code', '=', 'outgoing'), + ('name', 'ilike', 'BU/OUT/'), + ]) + + count = self.search_count([ + ('state', 'not in', ['cancel', 'draft', 'done']), ('picking_type_code', '=', 'outgoing') ]) for picking in pickings: - fullfillments = self.env['sales.order.fullfillment'].search([ - ('sales_order_id', '=', picking.sale_id.id) + fullfillments = self.env['sales.order.fulfillment.v2'].search([ + ('sale_order_id', '=', picking.sale_id.id) + ]) + + picking.state_reserve = 'ready' + picking.date_reserved = picking.date_reserved or datetime.datetime.utcnow() + + if any(rec.so_qty != rec.reserved_stock_qty for rec in fullfillments): + picking.state_reserve = 'waiting' + picking.date_reserved = '' + + self.check_state_reserve_backorder() + + def check_state_reserve_backorder(self): + pickings = self.search([ + ('backorder_id', '!=', False), + ('name', 'ilike', 'BU/OUT/'), + ('picking_type_code', '=', 'outgoing'), + ('state', 'not in', ['cancel', 'draft', 'done']) + ]) + + count = self.search_count([ + ('backorder_id', '!=', False), + ('picking_type_code', '=', 'outgoing'), + ('state', 'not in', ['cancel', 'draft', 'done']) + ]) + + for picking in pickings: + fullfillments = self.env['sales.order.fulfillment.v2'].search([ + ('sale_order_id', '=', picking.sale_id.id) ]) picking.state_reserve = 'ready' picking.date_reserved = picking.date_reserved or datetime.datetime.utcnow() - if any(rec.reserved_from not in ['Inventory On Hand', 'Reserved from stock', 'Free Stock'] for rec in fullfillments): + if any(rec.so_qty != rec.reserved_stock_qty for rec in fullfillments): picking.state_reserve = 'waiting' picking.date_reserved = '' @@ -901,6 +935,7 @@ class StockPicking(models.Model): 'res_id': wizard.id, 'target': 'new', } + self.send_mail_bills() return res diff --git a/indoteknik_custom/models/uangmuka_pembelian.py b/indoteknik_custom/models/uangmuka_pembelian.py index 204855d3..ba41f814 100644 --- a/indoteknik_custom/models/uangmuka_pembelian.py +++ b/indoteknik_custom/models/uangmuka_pembelian.py @@ -64,7 +64,7 @@ class UangmukaPembelian(models.TransientModel): param_debit = { 'move_id': account_move.id, - 'account_id': 401, # uang muka persediaan barang dagang + 'account_id': 669, # uang muka persediaan barang dagang 'partner_id': partner_id, 'currency_id': 12, 'debit': self.pay_amt, diff --git a/indoteknik_custom/models/uangmuka_penjualan.py b/indoteknik_custom/models/uangmuka_penjualan.py index 5acf604d..a3e95ecd 100644 --- a/indoteknik_custom/models/uangmuka_penjualan.py +++ b/indoteknik_custom/models/uangmuka_penjualan.py @@ -74,7 +74,7 @@ class UangmukaPenjualan(models.TransientModel): # sisa di credit untuk uang muka penjualan, diluar ongkir dan selisih param_credit = { 'move_id': account_move.id, - 'account_id': 449, # uang muka penjualan + 'account_id': 668, # penerimaan belum alokasi 'partner_id': partner_id, 'currency_id': 12, 'debit': 0, diff --git a/indoteknik_custom/models/user_company_request.py b/indoteknik_custom/models/user_company_request.py index af8a86ba..9216e8eb 100644 --- a/indoteknik_custom/models/user_company_request.py +++ b/indoteknik_custom/models/user_company_request.py @@ -104,9 +104,9 @@ class UserCompanyRequest(models.Model): self.user_company_id.active = True user.send_company_switch_approve_mail() if vals.get('is_switch_account') == True else user.send_company_request_approve_mail() else: - new_company = self.env['res.partner'].create({ - 'name': self.user_input - }) + # new_company = self.env['res.partner'].create({ + # 'name': self.user_input + # }) # self.user_id.parent_id = new_company.id user.send_company_request_reject_mail() return super(UserCompanyRequest, self).write(vals) diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py index f5261cd4..be4293a0 100644 --- a/indoteknik_custom/models/user_pengajuan_tempo_request.py +++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py @@ -7,7 +7,7 @@ class RejectReasonWizard(models.TransientModel): _description = 'Wizard for Reject Reason' request_id = fields.Many2one('user.pengajuan.tempo.request', string='Request') - reason_reject = fields.Text(string='Reason for Rejection', required=True) + reason_reject = fields.Text(string='Reason for Rejection', required=True, tracking=True) def confirm_reject(self): tempo = self.request_id @@ -55,7 +55,27 @@ class UserPengajuanTempoRequest(models.Model): ('approval_director', 'Approved by Director'), ('reject', 'Rejected'), ], string='Status', readonly=True, copy=False, index=True, track_visibility='onchange', default='draft') - reason_reject = fields.Char(string='Limit Tempo') + last_state_tempo = fields.Selection([ + ('draft', 'Pengajuan Tempo'), + ('approval_sales', 'Approved by Sales Manager'), + ('approval_finance', 'Approved by Finance'), + ('approval_director', 'Approved by Director'), + ('reject', 'Rejected'), + ], string='Status',) + reason_reject = fields.Char(string='Reason Reaject', tracking=True, track_visibility='onchange') + state_tempo_text = fields.Char(string="Status", compute="_compute_state_tempo_text") + + @api.depends('state_tempo') + def _compute_state_tempo_text(self): + for record in self: + status_mapping = { + 'draft': "Menunggu Approve Manager", + 'approval_sales': "Menunggu Approve Finance", + 'approval_finance': "Menunggu Approve Direktur", + 'approval_director': "Approved", + 'reject': "Rejected", + } + record.state_tempo_text = status_mapping.get(record.state_tempo, "Unknown") # informasi perusahaan name_tempo = fields.Many2one('res.partner', string='Nama Perusahaan', related='pengajuan_tempo_id.name_tempo', store=True, tracking=True, readonly=False) @@ -71,7 +91,7 @@ class UserPengajuanTempoRequest(models.Model): website_tempo = fields.Char(string='Website', related='pengajuan_tempo_id.website_tempo', store=True, tracking=True, readonly=False) portal = fields.Boolean(string='Portal Website', related='pengajuan_tempo_id.portal', store=True, tracking=True, readonly=False) estimasi_tempo = fields.Char(string='Estimasi Pembelian Pertahun', related='pengajuan_tempo_id.estimasi_tempo', store=True, tracking=True, readonly=False) - tempo_duration_origin = fields.Many2one('account.payment.term', string='Durasi Tempo', related='pengajuan_tempo_id.tempo_duration', store=True, tracking=True, readonly=False, domain=[('id', 'in', [24, 25, 29, 32])]) + tempo_duration_origin = fields.Many2one('account.payment.term', string='Durasi Tempo', related='tempo_duration', store=True, tracking=True, readonly=False, domain=[('id', 'in', [24, 25, 29, 32])]) tempo_limit_origin = fields.Char(string='Limit Tempo', related='pengajuan_tempo_id.tempo_limit' , store=True, tracking=True, readonly=False) category_produk_ids = fields.Many2many('product.public.category', string='Kategori Produk yang Digunakan', related='pengajuan_tempo_id.category_produk_ids', readonly=False) @@ -332,15 +352,15 @@ class UserPengajuanTempoRequest(models.Model): self.pengajuan_tempo_id.dokumen_tempat_bekerja = self.dokumen_tempat_bekerja @api.onchange('tempo_duration') - def _tempo_duration_change(self, vals): + def _tempo_duration_change(self): for tempo in self: - if tempo.env.user.id not in (7, 377, 12182): + if tempo.env.user.id not in (7, 688, 28, 377, 12182): 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, 377, 12182): + if tempo.env.user.id not in (7, 688, 28, 377, 12182): raise UserError("Limit tempo hanya bisa diubah oleh Sales Manager atau Direktur") def button_approve(self): for tempo in self: @@ -409,6 +429,9 @@ class UserPengajuanTempoRequest(models.Model): 'target': 'new', 'context': {'default_request_id': self.id}, } + def button_draft(self): + for tempo in self: + tempo.state_tempo = tempo.last_state_tempo if tempo.last_state_tempo else 'draft' def write(self, vals): is_approve = True if self.state_tempo == 'approval_director' or vals.get('state_tempo') == 'approval_director' else False @@ -507,10 +530,38 @@ class UserPengajuanTempoRequest(models.Model): # Buat kontak baru untuk company_id for contact_data in contacts_data: - self.env['res.partner'].create({ - "parent_id": self.user_company_id.id, # Hubungkan ke perusahaan - **contact_data, # Tambahkan data kontak - }) + existing_contact = self.env['res.partner'].search([ + ('parent_id', '=', self.user_company_id.id), + ('name', '=', contact_data['name']) + ], limit=1) + + if existing_contact: + # Pastikan tidak ada duplikasi nama dalam perusahaan yang sama + duplicate_check = self.env['res.partner'].search([ + ('name', '=', contact_data['name']), + ('id', '!=', existing_contact.id) # Hindari update yang menyebabkan duplikasi global + ], limit=1) + + if not duplicate_check: + # Perbarui hanya field yang tidak menyebabkan konflik + update_data = {k: v for k, v in contact_data.items() if k != 'name'} + existing_contact.write(update_data) + else: + raise UserError(f"Skipping update for {contact_data['name']} due to existing duplicate.") + else: + # Pastikan tidak ada partner lain dengan nama yang sama sebelum membuat baru + duplicate_check = self.env['res.partner'].search([ + ('name', '=', contact_data['name']) + ], limit=1) + + if duplicate_check: + # Jika nama sudah ada tetapi di perusahaan lain, tambahkan nama perusahaan + contact_data['name'] = f"{contact_data['name']} ({self.user_company_id.name})" + + self.env['res.partner'].create({ + "parent_id": self.user_company_id.id, # Hubungkan ke perusahaan + **contact_data, # Tambahkan data kontak + }) # Pengiriman self.user_company_id.pic_name = self.pengajuan_tempo_id.pic_name @@ -596,10 +647,10 @@ class UserPengajuanTempoRequest(models.Model): attachment_ids=[self.user_company_id.dokumen_tempat_bekerja.id]) # self.user_company_id.active = True # user.send_company_request_approve_mail() - self.user_company_id.property_payment_term_id = self.pengajuan_tempo_id.tempo_duration.id + self.user_company_id.property_payment_term_id = self.tempo_duration.id self.user_company_id.active_limit = True - self.user_company_id.warning_stage = float(limit_tempo) - (float(limit_tempo)/2) self.user_company_id.blocking_stage = limit_tempo + self.user_company_id.warning_stage = float(limit_tempo) - (float(limit_tempo)/2) # Internal Notes comment = [] diff --git a/indoteknik_custom/models/voucher.py b/indoteknik_custom/models/voucher.py index 37c97338..101d4bcf 100644 --- a/indoteknik_custom/models/voucher.py +++ b/indoteknik_custom/models/voucher.py @@ -59,7 +59,11 @@ class Voucher(models.Model): show_on_email = fields.Selection([ ('user_activation', 'User Activation') ], 'Show on Email') - + account_type = fields.Selection(string='Account Type', default="all", selection=[ + ('all', "All Account"), + ('person', "Account Individu"), + ('company', "Account Company"), + ]) @api.constrains('description') def _check_description_length(self): for record in self: diff --git a/indoteknik_custom/models/website_telegram.py b/indoteknik_custom/models/website_telegram.py new file mode 100644 index 00000000..c877a067 --- /dev/null +++ b/indoteknik_custom/models/website_telegram.py @@ -0,0 +1,193 @@ +import requests +# import clipboard +from odoo import models, fields, api +from telethon.sessions import StringSession +from telethon.sync import TelegramClient +from odoo.exceptions import UserError +from telethon import functions, types +from telethon.tl.types import InputPeerChannel +import asyncio +from telethon.tl.functions.messages import SendMessageRequest +# from telethon.tl.types import InputMediaPoll, Poll, PollAnswer, PeerChannel +import datetime +from telethon.tl.types import MessageMediaPoll +# from telethon.tl.functions.messages import SendMediaRequest +# from telethon.tl.types import TextWithEntities + +class WebsiteTelegram(models.Model): + _name = 'website.telegram' + _description = 'Telegram Channel' + + + tittle = fields.Char("Channel Title") + invite_link = fields.Char("Channel invite link") + username = fields.Char("Channel Name") + user_id = fields.Many2many('res.partner', string='User Member', store=True) + id_data = fields.Char("Channel ID") + about = fields.Char("Channel Description") + is_broadcast = fields.Boolean("Is Broadcast", default=True) + is_megagroup = fields.Boolean("Is Megagroup", default=False) + is_accept = fields.Boolean("Is Megagroup", default=False) + + # session_string = 'MIIBCgKCAQEAyMEdY1aR+sCR3ZSJrtztKTKqigvO/vBfqACJLZtS7QMgCGXJ6XIRyy7mx66W0/sOFa7/1mAZtEoIokDP3ShoqF4fVNb6XeqgQfaUHd8wJpDWHcR2OFwvplUUI1PLTktZ9uW2WE23b+ixNwJjJGwBDJPQEQFBE+vfmH0JP503wr5INS1poWg/j25sIWeYPHYeOrFp/eXaqhISP6G+q2IeTaWTXpwZj4LzXq5YOpk4bYEQ6mvRq7D1aHWfYmlEGepfaYR8Q0YqvvhYtMte3ITnuSJs171+GDqpdKcSwHnd6FudwGO4pcCOj4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB' + + + def create_channel(self, message): + asyncio.run(self._async_create_channel(message)) + + async def _async_create_channel(self, message): + async with TelegramClient(StringSession( + '1BVtsOJABu30MWCBFwYvvaYbFoIWi43r5a7TUZ2IWwrnSlXIwEhJS5k2y4UKjoDeMPKwhgUWn9lMk02zQjM0ZDzq61YyhkRBvZuu3hCxMsrtP92bkuZtL2g3g1VgI8s7rMhOD_WaGrZbuj-TmbTwIEbN5i1J4raDW2kYzmlLRCbT74xxrGjpzWCnVv7CSS9L2juXuut0lLMgli3_JZbqDO1IyBYh4ZFQYbMf7zv6moDR4MQp1qfnFArsikQcfxjlNXKFcSoyA_GjiIFfCuymwQVtdERXOAH03M_lm8fYbjvgxEkJvxR6hdCnYMcKpIujEEo9SmMmK7Vnl29g1TCPO5tlrDNXq3Ng='), + 27799517, 'df8ee44b0ed11108245037d47b511201') as client: + result = await client(functions.channels.CreateChannelRequest( + title=self.tittle, + about=self.about, + broadcast=False, + megagroup=True, + )) + channel = result.updates[3].message.peer_id + channel_link = await client(functions.messages.ExportChatInviteRequest( + peer=InputPeerChannel(channel_id=channel.channel_id, access_hash=result.chats[0].access_hash), + )) + self.invite_link = channel_link.link + + # Iterasi untuk setiap username + for name in self.user_id: + if name.telegram_id: + user_to_add = await client.get_entity(name.telegram_id) + result_add_user = await client(functions.channels.InviteToChannelRequest( + channel=channel, + users=[user_to_add.id], + )) + else: + raise UserError('ID Telegram '+ name.name + ' salah atau belum diisi') + # message = 'https://erp.indoteknik.com/web#id=' + self.id_data + '&action=209&model=sale.order&view_type=form&cids=1&menu_id=101' + message_template = 'https://erp.indoteknik.com/web#id=' + self.id_data + '&action=357&model=sale.order&view_type=form&cids=1&menu_id=212' + message2 = self.tittle + ' di print oleh ' + self.env.user.name + result_massage = await client(SendMessageRequest(channel, message_template)) + result_massage2 = await client(SendMessageRequest(channel, message)) + + # Send the poll to the channel using PeerChannel with the channel's ID + # result_polling = await client(SendMediaRequest( + # peer=InputPeerChannel(channel_id=channel.channel_id, access_hash=result.chats[0].access_hash), + # media=InputMediaPoll(poll=Poll( + # question=TextWithEntities(text="ASK RETURN?", entities=[]), + # answers=[ + # PollAnswer(text=TextWithEntities(text="Accept", entities=[]), option=b'\x01'), + # PollAnswer(text=TextWithEntities(text="Reject", entities=[]), option=b'\x02'), + # ], + # id=2 + # )), + # message='Ask Return Polling' + # )) + + def receive_messages(self): + return asyncio.run(self._async_receive_messages()) + + async def _async_receive_messages(self): + async with TelegramClient(StringSession( + '1BVtsOJABu30MWCBFwYvvaYbFoIWi43r5a7TUZ2IWwrnSlXIwEhJS5k2y4UKjoDeMPKwhgUWn9lMk02zQjM0ZDzq61YyhkRBvZuu3hCxMsrtP92bkuZtL2g3g1VgI8s7rMhOD_WaGrZbuj-TmbTwIEbN5i1J4raDW2kYzmlLRCbT74xxrGjpzWCnVv7CSS9L2juXuut0lLMgli3_JZbqDO1IyBYh4ZFQYbMf7zv6moDR4MQp1qfnFArsikQcfxjlNXKFcSoyA_GjiIFfCuymwQVtdERXOAH03M_lm8fYbjvgxEkJvxR6hdCnYMcKpIujEEo9SmMmK7Vnl29g1TCPO5tlrDNXq3Ng='), + 27799517, 'df8ee44b0ed11108245037d47b511201') as client: + channel = await client.get_entity(self.invite_link) + result = await client(functions.messages.GetHistoryRequest( + peer=channel, + offset_id=42, + offset_date=datetime.datetime(2024, 12, 31), + add_offset=0, + limit=100, + max_id=0, + min_id=0, + hash=channel.access_hash + )) + accept_found = False + + for message in result.messages: + if not isinstance(message.media, MessageMediaPoll): + continue + if message.media.results.total_voters > 0: + if message.media.results.results[0].voters > message.media.results.results[1].voters: + accept_found = True + break + + return accept_found + + def send_to_telegram(self, message): + # apiURL = f'https://api.telegram.org/bot{self.bot_name}/sendMessage' + try: + # requests.post(apiURL, json={'chat_id': self.chatID, 'text': message}) + asyncio.run(self._async_send_to_telegram(message)) + except Exception as e: + print(e) + + async def _async_send_to_telegram(self, message): + async with TelegramClient(StringSession( + '1BVtsOJABu30MWCBFwYvvaYbFoIWi43r5a7TUZ2IWwrnSlXIwEhJS5k2y4UKjoDeMPKwhgUWn9lMk02zQjM0ZDzq61YyhkRBvZuu3hCxMsrtP92bkuZtL2g3g1VgI8s7rMhOD_WaGrZbuj-TmbTwIEbN5i1J4raDW2kYzmlLRCbT74xxrGjpzWCnVv7CSS9L2juXuut0lLMgli3_JZbqDO1IyBYh4ZFQYbMf7zv6moDR4MQp1qfnFArsikQcfxjlNXKFcSoyA_GjiIFfCuymwQVtdERXOAH03M_lm8fYbjvgxEkJvxR6hdCnYMcKpIujEEo9SmMmK7Vnl29g1TCPO5tlrDNXq3Ng='), + 27799517, 'df8ee44b0ed11108245037d47b511201') as client: + channel = await client.get_entity(self.invite_link) + + # Iterasi untuk setiap username + for username in self.user_id: + if username: + try: + user_to_add = await client.get_entity(username.telegram_id) + + result_add_user = await client(functions.channels.InviteToChannelRequest( + channel=channel, + users=[user_to_add.id], + )) + except Exception as e: + print(f"Error adding user {username}: {e}") + + result_message = await client(SendMessageRequest(channel, message)) + + def test_send(self): + try: + self.env.cr.savepoint() + + self.env['website.telegram'].search([('tittle', '=', self.tittle)])[0].send_to_telegram('test message') + except Exception as e: + print(e) + + # @api.depends('name', 'chatID', 'bot_name') + # def _calc_python_code(self): + # for record in self: + # if not record.name: + # record.python_code = "pleas put a name" + # elif not record.test_message: + # record.test_message = "test" + # else: + # record.python_code = "self.env['website.telegram'].search([('name', '=', '" + record.name + "')])[0].send_to_telegram('" + record.test_message + "')" + # + # def copy_chat_id(self): + # record = self.browse(self.id) + # clipboard.copy(record.python_code) + # + # def paste_chat_id(self): + # record = self.browse(self.id) + # chat_id = clipboard.paste() + # record.write({'chatID': chat_id}) + # + # def paste_bot_name(self): + # record = self.browse(self.id) + # bot_name = clipboard.paste() + # record.write({'bot_name': bot_name}) + # + # def get_updates(self): + # apiURL = f'https://api.telegram.org/bot{self.bot_name}/getUpdates' + # try: + # response = requests.get(apiURL) + # updates = response.json() + # return updates + # except Exception as e: + # print(e) + # return None + + # def receive_messages(self): + # updates = self.get_updates() + # if updates and 'result' in updates: + # for update in updates['result']: + # if 'text' in update['channel_post']: + # message_text = update['channel_post']['text'] + # chat_id = update['channel_post']['chat']['id'] + # chat_name = update['channel_post']['chat']['username'] + # print(f"Received message: {message_text} from chat_id: {chat_id}, that is: {chat_name}") diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 83de8bda..18077441 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -24,6 +24,7 @@ access_website_categories_homepage,access.website.categories.homepage,model_webs access_website_categories_lob,access.website.categories.lob,model_website_categories_lob,,1,1,1,1 access_website_categories_management,access.website.categories.management,model_website_categories_management,,1,1,1,1 access_website_categories_management_line,access.website.categories.management.line,model_website_categories_management_line,,1,1,1,1 +access_website_telegram,access.website.telegram,model_website_telegram,,1,1,1,1 access_sales_target,access.sales.target,model_sales_target,,1,1,1,1 access_purchase_outstanding,access.purchase.outstanding,model_purchase_outstanding,,1,1,1,1 access_sales_outstanding,access.sales.outstanding,model_sales_outstanding,,1,1,1,1 @@ -104,6 +105,7 @@ access_purchase_order_multi_update,access.purchase.order.multi_update,model_purc access_invoice_reklas_penjualan,access.invoice.reklas.penjualan,model_invoice_reklas_penjualan,,1,1,1,1 access_invoice_reklas_penjualan_line,access.invoice.reklas.penjualan.line,model_invoice_reklas_penjualan_line,,1,1,1,1 access_purchase_order_multi_confirm,access.purchase.order.multi_confirm,model_purchase_order_multi_confirm,,1,1,1,1 +access_purchase_order_multi_ask_approval,access.purchase.order.multi_ask_approval,model_purchase_order_multi_ask_approval,,1,1,1,1 access_po_multi_cancel,access.po.multi.cancel,model_po_multi_cancel,,1,1,1,1 access_logbook_sj,access.logbook.sj,model_logbook_sj,,1,1,1,1 access_logbook_sj_line,access.logbook.sj.line,model_logbook_sj_line,,1,1,1,1 @@ -165,4 +167,7 @@ access_reject_reason_wizard,reject.reason.wizard,model_reject_reason_wizard,,1,1 access_confirm_approval_wizard,confirm.approval.wizard,model_confirm_approval_wizard,,1,1,1,0 access_barcode_product,access.barcode.product,model_barcode_product,,1,1,1,1 access_barcoding_product,access.barcoding.product,model_barcoding_product,,1,1,1,1 -access_barcoding_product_line,access.barcoding.product.line,model_barcoding_product_line,,1,1,1,1
\ No newline at end of file +access_barcoding_product_line,access.barcoding.product.line,model_barcoding_product_line,,1,1,1,1 +access_account_payment_register,access.account.payment.register,model_account_payment_register,,1,1,1,1 +access_stock_inventory,access.stock.inventory,model_stock_inventory,,1,1,1,1 +access_cancel_reason_order,cancel.reason.order,model_cancel_reason_order,,1,1,1,0 diff --git a/indoteknik_custom/views/account_move.xml b/indoteknik_custom/views/account_move.xml index 36b292e8..17263c3a 100644 --- a/indoteknik_custom/views/account_move.xml +++ b/indoteknik_custom/views/account_move.xml @@ -27,6 +27,7 @@ </field> <field name="invoice_date" position="after"> <field name="sale_id" readonly="1" attrs="{'invisible': [('move_type', '!=', 'out_invoice')]}"/> + <field name="purchase_order_id" readonly="1" attrs="{'invisible': [('move_type', '!=', 'in_invoice')]}"/> </field> <field name="ref" position="after"> <field name="sale_id" readonly="1" attrs="{'invisible': [('move_type', '!=', 'entry')]}"/> @@ -137,6 +138,9 @@ {'column_invisible': [('parent.move_type', '!=', 'in_invoice')]} </attribute> </field> + <field name="product_id" position="before"> + <field name="line_no" optional="hide"/> + </field> <field name="invoice_incoterm_id" position="after"> <field name="date_terima_tukar_faktur"/> </field> diff --git a/indoteknik_custom/views/barcoding_product.xml b/indoteknik_custom/views/barcoding_product.xml index 566655ff..c7473d39 100644 --- a/indoteknik_custom/views/barcoding_product.xml +++ b/indoteknik_custom/views/barcoding_product.xml @@ -8,6 +8,7 @@ <tree default_order="create_date desc"> <field name="product_id"/> <field name="quantity"/> + <field name="type"/> </tree> </field> </record> @@ -32,11 +33,13 @@ <group> <group> <field name="product_id" required="1"/> - <field name="quantity" required="1"/> + <field name="type" required="1"/> + <field name="quantity" attrs="{'invisible': [['type', 'in', ('barcoding')]], 'required': [['type', 'not in', ('barcoding')]]}"/> + <field name="barcode" attrs="{'invisible': [['type', 'in', ('print')]], 'required': [['type', 'not in', ('print')]]}"/> </group> </group> <notebook> - <page string="Line"> + <page string="Line" attrs="{'invisible': [['type', 'in', ('barcoding')]]}"> <field name="barcoding_product_line"/> </page> </notebook> diff --git a/indoteknik_custom/views/customer_commision.xml b/indoteknik_custom/views/customer_commision.xml index 0b72587e..51172b1c 100644 --- a/indoteknik_custom/views/customer_commision.xml +++ b/indoteknik_custom/views/customer_commision.xml @@ -12,6 +12,10 @@ <field name="commision_percent"/> <field name="commision_amt" readonly="1"/> <field name="status" readonly="1"/> + <field name="payment_status" readonly="1" + decoration-success="payment_status == 'payment'" + decoration-danger="payment_status == 'pending'" + widget="badge"/> <field name="brand_ids" widget="many2many_tags"/> </tree> </field> @@ -43,6 +47,10 @@ <button name="action_confirm_customer_commision" string="Confirm" type="object" options="{}"/> + <button name="action_confirm_customer_payment" + string="Konfirmasi Pembayaran" type="object" + options="{}" + attrs="{'invisible': [('payment_status', '==', 'payment')], 'readonly': [('payment_status', '=', 'payment')]}"/> </header> <sheet string="Customer Commision"> <div class="oe_button_box" name="button_box"/> @@ -68,6 +76,7 @@ <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> diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml index a70b2058..36c0db13 100755 --- a/indoteknik_custom/views/purchase_order.xml +++ b/indoteknik_custom/views/purchase_order.xml @@ -51,6 +51,9 @@ <field name="approval_status" position="after"> <field name="approval_status_unlock" invisible="True"/> </field> + <field name="partner_id" position="after"> + <field name="purchase_order_count"/> + </field> <field name="incoterm_id" position="after"> <field name="amount_total_without_service"/> <field name="delivery_amt"/> @@ -78,6 +81,9 @@ <field name="product_id" position="attributes"> <attribute name="options">{'no_create': True}</attribute> </field> + <field name="date_planned" position="attributes"> + <attribute name="invisible">1</attribute> + </field> <field name="product_qty" position="before"> <field name="is_edit_product_qty" readonly="1" optional="hide"/> <field name="qty_onhand" readonly="1" optional="hide"/> @@ -103,6 +109,10 @@ <field name="price_vendor" attrs="{'readonly': 1}" optional="hide"/> </field> <field name="price_subtotal" position="after"> + <field name="so_item_margin" attrs="{'readonly': 1}" optional="hide"/> + <field name="so_item_percent_margin" attrs="{'readonly': 1}" optional="hide"/> + <field name="item_margin" attrs="{'readonly': 1}" optional="hide"/> + <field name="item_percent_margin" attrs="{'readonly': 1}" optional="hide"/> <field name="so_line_id" attrs="{'readonly': 1}" optional="hide"/> <field name="so_id" attrs="{'readonly': 1}" optional="hide"/> <field name="indent" optional="hide"/> @@ -197,7 +207,7 @@ <field name="status_printed" optional="hide"/> </field> <field name="partner_id" position="after"> - <field name="store_name"/> + <field name="store_name" optional="hide"/> </field> </field> </record> @@ -290,6 +300,8 @@ <field name="partner_invoice_id"/> <field name="salesperson_id"/> <field name="product_id"/> + <field name="purchase_price_so"/> + <field name="purchase_price_po"/> <field name="qty_so"/> <field name="qty_po"/> <field name="margin_item" optional="hide"/> @@ -319,6 +331,15 @@ </record> </data> <data> + <record id="purchase_order_multi_ask_approval_ir_actions_server" model="ir.actions.server"> + <field name="name">Ask Approval PO</field> + <field name="model_id" ref="purchase.model_purchase_order"/> + <field name="binding_model_id" ref="purchase.model_purchase_order"/> + <field name="state">code</field> + <field name="code">action = records.open_form_multi_ask_approval_po()</field> + </record> + </data> + <data> <record id="purchase_order_multi_create_uangmuka_ir_actions_server" model="ir.actions.server"> <field name="name">Uang Muka</field> <field name="model_id" ref="purchase.model_purchase_order"/> diff --git a/indoteknik_custom/views/purchase_order_multi_ask_approval.xml b/indoteknik_custom/views/purchase_order_multi_ask_approval.xml new file mode 100644 index 00000000..887ac0bd --- /dev/null +++ b/indoteknik_custom/views/purchase_order_multi_ask_approval.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<odoo> + <data> + <record id="view_purchase_order_multi_ask_approval_po_form" model="ir.ui.view"> + <field name="name">Purchase Order Multi ask_approval</field> + <field name="model">purchase.order.multi_ask_approval</field> + <field name="arch" type="xml"> + <form> + <sheet> + <group> + <span>Apakah Anda Yakin Ingin Ask Approval PO Tersebut?</span> + </group> + </sheet> + <footer> + <button name="save_multi_ask_approval_po" string="Ask Approval PO" type="object" default_focus="1" class="oe_highlight"/> + <button string="Cancel" class="btn btn-secondary" special="cancel" /> + </footer> + </form> + </field> + </record> + + <record id="action_purchase_order_multi_ask_approval" model="ir.actions.act_window"> + <field name="name">Purchase Order Multi ask_approval</field> + <field name="res_model">purchase.order.multi_ask_approval</field> + <field name="type">ir.actions.act_window</field> + <field name="view_mode">form</field> + <field name="view_id" ref="view_purchase_order_multi_ask_approval_po_form"/> + <field name="target">new</field> + </record> + </data> +</odoo>
\ No newline at end of file diff --git a/indoteknik_custom/views/report_logbook_sj.xml b/indoteknik_custom/views/report_logbook_sj.xml index 896594bb..94f6c2ab 100644 --- a/indoteknik_custom/views/report_logbook_sj.xml +++ b/indoteknik_custom/views/report_logbook_sj.xml @@ -77,6 +77,16 @@ </field> </record> + <record id="report_logbook_sj_view_search" model="ir.ui.view"> + <field name="name">report.logbook.sj.search.view</field> <!-- Made the name more descriptive --> + <field name="model">report.logbook.sj</field> + <field name="arch" type="xml"> + <search string="Search Report"> + <field name="sj_number"/> + </search> + </field> + </record> + <record id="report_logbook_sj_action" model="ir.actions.act_window"> <field name="name">Report Logbook SJ</field> <field name="type">ir.actions.act_window</field> diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml index bd664890..af5e0db3 100644 --- a/indoteknik_custom/views/res_partner.xml +++ b/indoteknik_custom/views/res_partner.xml @@ -68,6 +68,7 @@ <field name="vat" position="after"> <field name="email_finance" widget="email"/> <field name="email_sales" widget="email"/> + <field name="telegram_id"/> <field name="use_so_approval" attrs="{'invisible': [('parent_id', '!=', False), ('company_type', '!=', 'company')]}" /> <field name="use_only_ready_stock" attrs="{'invisible': [('parent_id', '!=', False), ('company_type', '!=', 'company')]}" /> <field name="web_role" attrs="{'invisible': ['|', ('parent_id', '=', False), ('company_type', '=', 'company')]}" /> diff --git a/indoteknik_custom/views/res_users.xml b/indoteknik_custom/views/res_users.xml index 9553bb91..5b35f9c4 100644 --- a/indoteknik_custom/views/res_users.xml +++ b/indoteknik_custom/views/res_users.xml @@ -236,6 +236,7 @@ <tr><td style="padding-bottom: 16px;"><b>Pembayaran Lengkap:</b> Pilih metode pembayaran yang sesuai dengan kebutuhan Anda, baik transfer bank, VA, kartu kredit, atau pembayaran tempo.</td></tr> <tr><td style="padding-bottom: 16px;"><b>Pelacakan Pengiriman:</b> Lacak status pengiriman pesanan Anda secara real-time.</td></tr> <tr><td style="padding-bottom: 16px;">Untuk memulai transaksi, silakan login Kembali menggunakan akun Anda di <a href="https://indoteknik.com/login">Indoteknik.com</a></td></tr> + <tr><td style="padding-bottom: 16px;">Gunakan kode voucher berikut untuk mendapatkan diskon belanja hingga 10 juta: <strong>${object.get_voucher_code('switch_account')}</strong></td></tr> <tr><td style="padding-bottom: 16px;">Kami sangat senang dapat melayani Anda. Jika ada pertanyaan atau membutuhkan bantuan, jangan ragu untuk menghubungi tim layanan Customer kami di Whatsapp <a href="https://wa.me/6281717181922">0817-1718-1922</a> atau <a href="mailto:sales@indoteknik.com">sales@indoteknik.com</a></td></tr> <tr><td style="padding-bottom: 2px;"><b>Hormat kami,</b></td></tr> @@ -299,6 +300,7 @@ <tr><td style="padding-bottom: 16px;">Selamat! Akun bisnis Anda di indoteknik.com sekarang sudah resmi menjadi PKP dari yang sebelumnya Non-PKP.</td></tr> <tr><td style="padding-bottom: 16px;">Jangan lupa untuk mengecek kembali semua data perusahaan kamu, ya. Pastikan NPWP dan informasi Perusahaan lainnya sudah kamu isi dengan benar.</td></tr> + <tr><td style="padding-bottom: 16px;">Gunakan kode voucher berikut untuk mendapatkan diskon belanja hingga 10 juta: <strong>${object.get_voucher_code('switch_account')}</strong></td></tr> <tr><td style="padding-bottom: 16px;">Kamu juga dapat mengubah informasi perusahaan dengan mengunjungi profil atau <a href="https://indoteknik.com/my/profile">klik disini</a></td></tr> <tr><td style="padding-bottom: 2px;">Industrial Supply & Solutions</td></tr> diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 4d31b072..8158774b 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -111,7 +111,7 @@ <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"> + <attribute name="modifiers"> {'readonly': [('desc_updatable', '=', False)]} </attribute> </xpath> @@ -286,6 +286,31 @@ </page> </field> </record> + <!-- Wizard for Reject Reason --> + <record id="view_cancel_reason_order_form" model="ir.ui.view"> + <field name="name">cancel.reason.order.form</field> + <field name="model">cancel.reason.order</field> + <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="nomor_so_pengganti" 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"/> + </footer> + </form> + </field> + </record> + + <record id="action_cancel_reason_order" model="ir.actions.act_window"> + <field name="name">Cancel Reason</field> + <field name="res_model">cancel.reason.order</field> + <field name="view_mode">form</field> + <field name="target">new</field> + </record> </data> <data> <record id="sale_order_tree_view_inherit" model="ir.ui.view"> @@ -299,6 +324,7 @@ <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> </field> </record> diff --git a/indoteknik_custom/views/stock_inventory.xml b/indoteknik_custom/views/stock_inventory.xml new file mode 100644 index 00000000..db85f05c --- /dev/null +++ b/indoteknik_custom/views/stock_inventory.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<odoo> + <!-- Form View: Tambahkan field 'number' setelah lokasi --> + <record id="view_stock_inventory_form_inherit" model="ir.ui.view"> + <field name="name">stock.inventory.form.inherit</field> + <field name="model">stock.inventory</field> + <field name="inherit_id" ref="stock.view_inventory_form"/> + <field name="arch" type="xml"> + <xpath expr="//field[@name='location_ids']" position="after"> + <field name="number" readonly="1"/> + <field name="adjusment_type" /> + </xpath> + </field> + </record> + + <!-- Tree View: Tambahkan field 'number' setelah tanggal --> + <record id="view_stock_inventory_tree_inherit" model="ir.ui.view"> + <field name="name">stock.inventory.tree.inherit</field> + <field name="model">stock.inventory</field> + <field name="inherit_id" ref="stock.view_inventory_tree"/> + <field name="arch" type="xml"> + <xpath expr="//field[@name='date']" position="after"> + <field name="number"/> + </xpath> + </field> + </record> + +</odoo> diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index 016fbf17..03fc8f87 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -89,9 +89,9 @@ <field name="partner_id" position="after"> <field name="real_shipping_id"/> </field> - <field name="product_uom_qty" position="attributes"> + <!-- <field name="product_uom_qty" position="attributes"> <attribute name="attrs">{'readonly': [('parent.picking_type_code', '=', 'incoming')]}</attribute> - </field> + </field> --> <field name="date_done" position="after"> <field name="arrival_time"/> </field> diff --git a/indoteknik_custom/views/user_pengajuan_tempo.xml b/indoteknik_custom/views/user_pengajuan_tempo.xml index 33ad91cf..7f1faa41 100644 --- a/indoteknik_custom/views/user_pengajuan_tempo.xml +++ b/indoteknik_custom/views/user_pengajuan_tempo.xml @@ -269,7 +269,7 @@ <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">finance@indoteknik.co.id, stephan@indoteknik.co.id, sapiabon768@gmail.com</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;"> diff --git a/indoteknik_custom/views/user_pengajuan_tempo_request.xml b/indoteknik_custom/views/user_pengajuan_tempo_request.xml index bb8262c9..7063231b 100644 --- a/indoteknik_custom/views/user_pengajuan_tempo_request.xml +++ b/indoteknik_custom/views/user_pengajuan_tempo_request.xml @@ -10,7 +10,17 @@ <field name="create_date"/> <field name="tempo_duration" attrs="{'readonly': [('state_tempo', '=', 'approval_director')]}"/> <field name="tempo_limit" attrs="{'readonly': [('state_tempo', '=', 'approval_director')]}" placeholder="Contoh format, misalnya '10000000'" widget="monetary" options="{'currency_field': 'currency_id'}"/> - <field name="state_tempo" decoration-success="state_tempo == 'approval_director'" decoration-danger="state_tempo == 'reject'" widget="badge" optional="show"/> +<!-- <field name="state_tempo"--> +<!-- decoration-danger="state_tempo == 'reject'"--> +<!-- decoration-warning="state_tempo in ('draft', 'approval_sales', 'approval_finance')"--> +<!-- decoration-success="state_tempo == 'approval_director'">--> +<!-- <tree>--> +<!-- <field name="state_tempo" invisible="1"/>--> +<!-- <field name="state_tempo_text" string="Status"/>--> +<!-- </tree>--> +<!-- </field>--> + <field name="state_tempo" invisible="1"/> + <field name="state_tempo_text" decoration-success="state_tempo_text == 'Approved'" decoration-danger="state_tempo_text == 'Rejected'" widget="badge" optional="show"/> </tree> </field> </record> @@ -30,7 +40,11 @@ string="Reject" attrs="{'invisible': [('state_tempo', 'in', ['approval_director','reject'])]}" type="object" - groups="purchase.group_purchase_manager" + class="oe_highlight"/> + <button name="button_draft" + string="Reset to Draft" + attrs="{'invisible': [('state_tempo', '!=', 'reject')]}" + type="object" class="oe_highlight"/> <field name="state_tempo" widget="statusbar" statusbar_visible="draft,approval_sales,approval_finance,approval_director" diff --git a/indoteknik_custom/views/voucher.xml b/indoteknik_custom/views/voucher.xml index 29d0ad4b..ae958f05 100755 --- a/indoteknik_custom/views/voucher.xml +++ b/indoteknik_custom/views/voucher.xml @@ -35,6 +35,7 @@ <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])]"/> </group> diff --git a/indoteknik_custom/views/website_telegram.xml b/indoteknik_custom/views/website_telegram.xml new file mode 100644 index 00000000..d8590fc0 --- /dev/null +++ b/indoteknik_custom/views/website_telegram.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<odoo> + <data> + <record id="website_telegram_action" model="ir.actions.act_window"> + <field name="name">Channel Telegram</field> + <field name="res_model">website.telegram</field> + <field name="view_mode">tree,form</field> + </record> + + <record id="website_telegram_tree" model="ir.ui.view"> + <field name="name">website.telegram.tree</field> + <field name="model">website.telegram</field> + <field name="arch" type="xml"> + <tree string="Channel List"> + <field name="tittle"/> + <field name="about"/> + <field name="user_id"/> + <button name="test_send" type="object" icon="fa-paper-plane" string="Send Test"/> +<!-- <button name="create_channel" type="object" icon="fa-paper-plane" string="Create Channel"/>--> +<!-- <button name="receive_messages" type="object" icon="fa-paper-plane" string="GET Test"/>--> + </tree> + </field> + </record> + + <record id="website_telegram_form" model="ir.ui.view"> + <field name="name">website.telegram.form</field> + <field name="model">website.telegram</field> + <field name="arch" type="xml"> + <form create="0"> + <sheet> + <group> + <group> + <field name="tittle"/> + <field name="about"/> + <field name="user_id" widget="many2many_tags"/> + </group> + </group> + </sheet> + </form> + </field> + </record> + + <record id="ir_actions_server_website_telegram_sync_to_solr" model="ir.actions.server"> + <field name="name">Sync to solr</field> + <field name="model_id" ref="indoteknik_custom.model_website_telegram"/> + <field name="binding_model_id" ref="indoteknik_custom.model_website_telegram"/> + <field name="state">code</field> + <field name="code">model.action_sync_to_solr()</field> + </record> + + <menuitem + id="website_telegram" + name="Channel Telegram" + parent="website_sale.menu_orders" + sequence="1" + action="website_telegram_action" + /> + </data> +</odoo> |
