diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2024-11-29 10:05:58 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2024-11-29 10:05:58 +0700 |
| commit | 8906f678c23090d70d16191dc1fe76e518e8e9d9 (patch) | |
| tree | a98cb41a39064f692d764338177b84fd14bed82a | |
| parent | d0bd4a82c923789a931f9433085f4219c6d7346a (diff) | |
| parent | 0080b6b1da5f181cee32fae7bb5166ce65165374 (diff) | |
Merge branch 'production' into CR/quotation-noPo
# Conflicts:
# indoteknik_custom/models/user_company_request.py
42 files changed, 913 insertions, 257 deletions
diff --git a/indoteknik_api/controllers/api_v1/product.py b/indoteknik_api/controllers/api_v1/product.py index 9673b3ef..32362582 100644 --- a/indoteknik_api/controllers/api_v1/product.py +++ b/indoteknik_api/controllers/api_v1/product.py @@ -96,6 +96,21 @@ class Product(controller.Controller): return self.response(data, headers=[('Cache-Control', 'max-age=600, private')]) + @http.route(prefix + 'product_variant/<id>/qty_available', auth='public', methods=['GET', 'OPTIONS']) + @controller.Controller.must_authorized() + def get_product_variant_stock_available_by_id(self, **kw): + id = int(kw.get('id')) + product = request.env['product.product'].search( + [('id', '=', id)], limit=1) + + qty_available = product.qty_free_bandengan + + data = { + 'qty': qty_available, + } + + return self.response(data, headers=[('Cache-Control', 'max-age=600, private')]) + @http.route(prefix + 'product/template/price/<id>', auth='public', methods=['GET', 'OPTIONS']) def get_product_template_price_by_id(self, **kw): if not self.authenticate(): diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py index 99ba2573..8b95ade8 100644 --- a/indoteknik_api/controllers/api_v1/sale_order.py +++ b/indoteknik_api/controllers/api_v1/sale_order.py @@ -450,7 +450,8 @@ class SaleOrder(controller.Controller): 'company_id': 1, 'order_id': sale_order.id, 'product_id': cart['id'], - 'product_uom_qty': cart['quantity'] + 'product_uom_qty': cart['quantity'], + 'product_available_quantity': cart['available_quantity'] }) order_line.product_id_change() order_line.onchange_vendor_id() @@ -467,6 +468,7 @@ class SaleOrder(controller.Controller): if len(promotions) > 0: sale_order.apply_promotion_program() + sale_order.add_free_product(promotions) voucher_code = params['value']['voucher'] voucher = request.env['voucher'].search([('code', '=', voucher_code),('apply_type', 'in', ['all', 'brand'])], limit=1) diff --git a/indoteknik_api/controllers/api_v1/stock_picking.py b/indoteknik_api/controllers/api_v1/stock_picking.py index ea8c6400..110cde8a 100644 --- a/indoteknik_api/controllers/api_v1/stock_picking.py +++ b/indoteknik_api/controllers/api_v1/stock_picking.py @@ -32,7 +32,9 @@ class StockPicking(controller.Controller): pending_domain = [('driver_departure_date', '=', False), ('driver_arrival_date', '=', False)] shipment_domain = [('driver_departure_date', '!=', False), ('driver_arrival_date', '=', False)] - completed_domain = [('driver_departure_date', '!=', False), ('driver_arrival_date', '!=', False)] + shipment_domain2 = [('driver_departure_date', '!=', False), ('sj_return_date', '=', False)] + completed_domain = [('driver_departure_date', '!=', False),'|', ('driver_arrival_date', '!=', False), ('sj_return_date', '!=', False)] + completed_domain2 = [('driver_departure_date', '!=', False), ('sj_return_date', '!=', False)] picking_model = request.env['stock.picking'] domain = [ @@ -74,7 +76,7 @@ class StockPicking(controller.Controller): 'name': picking.sale_id.name, 'client_order_ref': picking.sale_id.client_order_ref or '' }, - 'delivered': picking.waybill_id.delivered or picking.driver_arrival_date != False, + 'delivered': picking.waybill_id.delivered or picking.driver_arrival_date != False or picking.sj_return_date != False, 'status': picking.shipping_status, 'carrier_name': picking.carrier_id.name or '', 'last_manifest': next(iter(manifests), None) @@ -83,8 +85,8 @@ class StockPicking(controller.Controller): return self.response({ 'summary': { 'pending_count': picking_model.search_count(default_domain + pending_domain), - 'shipment_count': picking_model.search_count(default_domain + shipment_domain), - 'completed_count': picking_model.search_count(default_domain + completed_domain) + 'shipment_count': picking_model.search_count(default_domain + shipment_domain + shipment_domain2), + 'completed_count': picking_model.search_count(default_domain + completed_domain ) }, 'picking_total': picking_model.search_count(domain), 'pickings': res_pickings diff --git a/indoteknik_api/models/product_product.py b/indoteknik_api/models/product_product.py index 386ddb6a..f8869c7d 100644 --- a/indoteknik_api/models/product_product.py +++ b/indoteknik_api/models/product_product.py @@ -61,8 +61,9 @@ class ProductProduct(models.Model): price_for = self.env.context.get('price_for', 'odoo') pricelist = pricelist or self.env.context.get('user_pricelist') default_price_tier = '1_v2' - - price_tier = pricelist.get_tier_level() + price_tier = [] + if pricelist: + price_tier = pricelist.get_tier_level() price_tier = price_tier if price_tier else default_price_tier pricelist = self._get_pricelist_tier(price_tier) diff --git a/indoteknik_api/models/sale_order.py b/indoteknik_api/models/sale_order.py index 3f9606a5..0771f97a 100644 --- a/indoteknik_api/models/sale_order.py +++ b/indoteknik_api/models/sale_order.py @@ -33,7 +33,7 @@ class SaleOrder(models.Model): 'id': picking.id, 'name': picking.name, 'tracking_number': picking.delivery_tracking_no or '', - 'delivered': picking.waybill_id.delivered or picking.driver_arrival_date != False, + 'delivered': picking.waybill_id.delivered or picking.driver_arrival_date != False or picking.sj_return_date != False, }) if sale_order.state == 'cancel': data['status'] = 'cancel' @@ -86,6 +86,7 @@ class SaleOrder(models.Model): 'subtotal': line.price_subtotal } product['quantity'] = line.product_uom_qty + product['available_quantity'] = line.product_available_quantity data_with_detail['products'].append(product) for invoice in sale_order.invoice_ids: if invoice.state == 'posted': diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index c8a658b5..89f62524 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -145,6 +145,9 @@ 'views/approval_unreserve.xml', 'views/vendor_approval.xml', 'views/find_page.xml', + 'views/approval_retur_picking.xml', + 'views/form_vendor_approval_multi_approve.xml', + 'views/form_vendor_approval_multi_reject.xml', 'report/report.xml', 'report/report_banner_banner.xml', 'report/report_banner_banner2.xml', diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index e62fbb4a..ad6d75dd 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -131,3 +131,6 @@ from . import approval_unreserve from . import vendor_approval from . import partner from . import find_page +from . import approval_retur_picking +from . import va_multi_approve +from . import va_multi_reject diff --git a/indoteknik_custom/models/approval_date_doc.py b/indoteknik_custom/models/approval_date_doc.py index 441ada3d..751bae82 100644 --- a/indoteknik_custom/models/approval_date_doc.py +++ b/indoteknik_custom/models/approval_date_doc.py @@ -40,6 +40,7 @@ class ApprovalDateDoc(models.Model): raise UserError("Hanya Accounting Yang Bisa Approve") self.check_invoice_so_picking self.picking_id.driver_departure_date = self.driver_departure_date + self.picking_id.date_doc_kirim = self.driver_departure_date self.state = 'done' self.approve_date = datetime.utcnow() self.approve_by = self.env.user.id diff --git a/indoteknik_custom/models/approval_retur_picking.py b/indoteknik_custom/models/approval_retur_picking.py new file mode 100644 index 00000000..34c449a8 --- /dev/null +++ b/indoteknik_custom/models/approval_retur_picking.py @@ -0,0 +1,38 @@ +from odoo import models, fields +import logging + +_logger = logging.getLogger(__name__) + + +class ApprovalReturPicking(models.TransientModel): + _name = 'approval.retur.picking' + _description = 'Wizard to add Note Return' + + note_return = fields.Text(string="Note Return") + + def action_confirm_note_return(self): + picking_ids = self._context.get('picking_ids') + picking = self.env['stock.picking'].browse(picking_ids) + + # Update the note_return field + picking.update({ + 'approval_return_status': 'pengajuan1' + }) + + # Post a highlighted message to lognote + # picking.message_post( + # body=f"<div style='background-color: #fdf2e9; border: 1px solid #f5c6cb; padding: 10px;'>" + # f"<b>Note Return (Pinned):</b><br>{self.note_return}</div>", + # subtype_id=self.env.ref("mail.mt_note").id + # ) + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Notification', + 'message': 'Status pengajuan telah berubah', + 'next': {'type': 'ir.actions.act_window_close'}, + } + } + diff --git a/indoteknik_custom/models/approval_unreserve.py b/indoteknik_custom/models/approval_unreserve.py index ba8b8da7..d847ea37 100644 --- a/indoteknik_custom/models/approval_unreserve.py +++ b/indoteknik_custom/models/approval_unreserve.py @@ -86,7 +86,7 @@ class ApprovalUnreserve(models.Model): }) # Trigger the unreserve function self._trigger_unreserve() - self.picking_id.check_state_reserve() + # self.picking_id.check_state_reserve() def action_reject(self, reason): if self.env.user.id != self.user_id.id: diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index 548115e6..4e96e6d4 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -401,36 +401,40 @@ class AutomaticPurchase(models.Model): domain = [ ('product_id', '=', line.product_id.id) ] - sale = self.env['v.sales.outstanding'].search(domain, order='sale_order_create_date desc', limit=1) - - existing_match = self.env['automatic.purchase.sales.match'].search([ - ('automatic_purchase_id', '=', self.id), - ('sale_id', '=', sale.sale_id.id), - ('product_id', '=', sale.product_id.id), - ]) + sales = self.env['v.sales.outstanding'].search(domain, order='sale_order_create_date desc') - price_so = self.env['sale.order.line'].search([ - ('id', '=', sale.sale_line_id.id), - ]) - - if existing_match: - continue + for sale in sales: + existing_match = self.env['automatic.purchase.sales.match'].search([ + ('automatic_purchase_id', '=', self.id), + ('sale_id', '=', sale.sale_id.id), + ('sale_line_id', '=', sale.sale_line_id.id), + ('product_id', '=', sale.product_id.id), + ]) - self.env['automatic.purchase.sales.match'].create([{ - 'automatic_purchase_id': self.id, - 'sale_id': sale.sale_id.id, - 'sale_line_id': sale.sale_line_id.id, - 'picking_id': sale.picking_id.id, - 'move_id': sale.move_id.id, - 'partner_id': sale.partner_id.id, - 'partner_invoice_id': sale.partner_invoice_id.id, - 'salesperson_id': sale.salesperson_id.id, - 'product_id': sale.product_id.id, - 'qty_so': sale.outgoing, - 'qty_po': line.qty_purchase, - 'purchase_price': price_so.purchase_price, - 'purchase_tax_id': price_so.purchase_tax_id.id if price_so.purchase_tax_id.id else None, - }]) + # price_so = self.env['sale.order.line'].search([ + # ('id', '=', sale.sale_line_id.id), + # ]) + + if existing_match: + continue + + self.env['automatic.purchase.sales.match'].create([{ + 'automatic_purchase_id': self.id, + 'sale_id': sale.sale_id.id, + 'sale_line_id': sale.sale_line_id.id, + 'picking_id': sale.picking_id.id, + 'move_id': sale.move_id.id, + 'partner_id': sale.partner_id.id, + 'partner_invoice_id': sale.partner_invoice_id.id, + 'salesperson_id': sale.salesperson_id.id, + 'product_id': sale.product_id.id, + 'qty_so': sale.outgoing, + 'qty_po': line.qty_purchase, + 'purchase_price': sale.sale_line_id.purchase_price, + 'purchase_tax_id': sale.sale_line_id.purchase_tax_id.id if sale.sale_line_id.purchase_tax_id.id else None, + # 'purchase_price': price_so.purchase_price, + # 'purchase_tax_id': price_so.purchase_tax_id.id if price_so.purchase_tax_id.id else None, + }]) def _create_sync_purchasing_job(self, jobs): date = datetime.utcnow() @@ -462,9 +466,9 @@ class AutomaticPurchase(models.Model): count = 0 for point in orderpoints: # _logger.info('test %s' % point.product_id.name) - if point.product_id.virtual_available > point.product_min_qty: + if point.product_id.qty_available_bandengan > point.product_min_qty: continue - qty_purchase = point.product_max_qty - point.product_id.virtual_available + qty_purchase = point.product_max_qty - point.product_id.qty_available_bandengan po_line = self.env['purchase.order.line'].search([('product_id', '=', point.product_id.id), ('order_id.state', '=', 'done')], order='id desc', limit=1) if self.vendor_id: @@ -491,7 +495,7 @@ class AutomaticPurchase(models.Model): 'qty_purchase': qty_purchase, 'qty_min': point.product_min_qty, 'qty_max': point.product_max_qty, - 'qty_available': point.product_id.virtual_available, + 'qty_available': point.product_id.qty_available_bandengan, # 'partner_id': po_line.order_id.partner_id.id, # 'last_price': po_line.price_unit, 'partner_id': vendor_id, diff --git a/indoteknik_custom/models/delivery_order.py b/indoteknik_custom/models/delivery_order.py index 2ed49a54..3473197b 100644 --- a/indoteknik_custom/models/delivery_order.py +++ b/indoteknik_custom/models/delivery_order.py @@ -33,11 +33,13 @@ class DeliveryOrder(models.TransientModel): picking.driver_id = self.env.uid picking.delivery_tracking_no = line_tracking_no + if picking.driver_departure_date: + picking.sj_return_date = datetime.utcnow() + delivery_type = self.env['delivery.order.line'].get_delivery_type(picking.driver_departure_date, picking.driver_arrival_date) if delivery_type == 'departure': picking.driver_departure_date = current_time elif delivery_type == 'arrival': - picking.sj_return_date = datetime.utcnow() sale_order = False if picking.origin: diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index e64b63d7..2e80beec 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -61,6 +61,7 @@ class ProductTemplate(models.Model): sni = fields.Boolean(string='SNI') tkdn = fields.Boolean(string='TKDN') short_spesification = fields.Char(string='Short Spesification') + merchandise_ok = fields.Boolean(string='Product Promotion') @api.constrains('name', 'internal_reference', 'x_manufacture') def required_public_categ_ids(self): @@ -374,6 +375,10 @@ class ProductProduct(models.Model): is_edited = fields.Boolean(string='Is Edited') qty_sold = fields.Float(string='Sold Quantity', compute='_get_qty_sold') short_spesification = fields.Char(string='Short Spesification') + max_qty_reorder = fields.Float(string='Max Qty Reorder', compute='_get_max_qty_reordering_rule') + qty_rpo = fields.Float(string='Qty RPO', compute='_get_qty_rpo') + plafon_qty = fields.Float(string='Max Plafon', compute='_get_plafon_qty_product') + merchandise_ok = fields.Boolean(string='Product Promotion') def _get_clean_website_description(self): for rec in self: @@ -487,6 +492,16 @@ class ProductProduct(models.Model): qty = sum(qty_incoming.mapped('product_uom_qty')) product.qty_incoming_bandengan = qty + def _get_qty_incoming_bandengan_with_exclude(self): + for product in self: + qty_incoming = self.env['stock.move'].search([ + ('product_id', '=', product.id), + ('location_dest_id', 'in', [57, 83]), + ('state', 'not in', ['done', 'cancel']) + ]) + qty = sum(qty_incoming.mapped('product_uom_qty')) + product.qty_incoming_bandengan = qty + def _get_qty_outgoing_bandengan(self): for product in self: qty_incoming = self.env['stock.move'].search([ @@ -510,13 +525,41 @@ class ProductProduct(models.Model): def _get_qty_available_bandengan(self): for product in self: qty_available = product.qty_incoming_bandengan + product.qty_onhand_bandengan - product.qty_outgoing_bandengan - product.qty_available_bandengan = qty_available + product.qty_available_bandengan = qty_available or 0 def _get_qty_free_bandengan(self): for product in self: qty_free = product.qty_onhand_bandengan - product.qty_outgoing_bandengan product.qty_free_bandengan = qty_free - + + def _get_max_qty_reordering_rule(self): + for product in self: + reordering = self.env['stock.warehouse.orderpoint'].search([ + ('product_id', '=', product.id) + ], limit=1) + if not reordering: + product.max_qty_reorder = 0 + else: + product.max_qty_reorder = reordering.product_max_qty + + def _get_qty_rpo(self): + for product in self: + rpo = self.env['v.requisition.match.po'].search([ + ('product_id', '=', product.id) + ], limit=1) + if not rpo: + product.qty_rpo = 0 + else: + product.qty_rpo = rpo.qty_rpo + + def _get_plafon_qty_product(self): + for product in self: + qty_available = product.qty_available_bandengan + max_qty = product.max_qty_reorder + qty_rpo = product.qty_rpo + product.plafon_qty = max_qty - qty_available + qty_rpo + + # def write(self, vals): # if 'solr_flag' not in vals: # for variant in self: diff --git a/indoteknik_custom/models/promotion/sale_order.py b/indoteknik_custom/models/promotion/sale_order.py index cb9a6f92..1c31d060 100644 --- a/indoteknik_custom/models/promotion/sale_order.py +++ b/indoteknik_custom/models/promotion/sale_order.py @@ -6,6 +6,17 @@ class SaleOrder(models.Model): order_promotion_ids = fields.One2many('sale.order.promotion', 'order_id', 'Promotions') + def add_free_product(self, promotions): + for promotion in promotions: + program_line = self.env['promotion.program.line'].browse(promotion['program_line_id']) + for free_product in program_line.free_product_ids: + if free_product.product_id.merchandise_ok: + self.env['sale.order.line'].create({ + 'order_id': self.id, + 'name': f"Free Product {free_product.product_id.display_name} Quantity ({free_product.qty})", + 'display_type': 'line_note' + }) + def apply_promotion_program(self): userdata = { 'user_id': self.partner_id.user_id.id, diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index ef86bc61..9388ae4c 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -72,6 +72,8 @@ class PurchaseOrder(models.Model): grand_total = fields.Monetary(string='Grand Total', help='Amount total + amount delivery', compute='_compute_grand_total') total_margin_match = fields.Float(string='Total Margin Match', compute='_compute_total_margin_match') approve_by = fields.Many2one('res.users', string='Approve By') + exclude_incoming = fields.Boolean(string='Exclude Incoming', default=False, + help='Centang jika tidak mau masuk perhitungan Incoming Qty') def _compute_total_margin_match(self): for purchase in self: @@ -583,10 +585,29 @@ class PurchaseOrder(models.Model): picking.scheduled_date = self.date_planned picking.date_deadline = self.date_planned + def _check_qty_plafon_product(self): + for line in self.order_line: + if not line.product_id: + continue + # test = line.product_uom_qty + # test2 = line.product_id.plafon_qty + # test3 = test2 + line.product_uom_qty + if line.product_uom_qty > line.product_id.plafon_qty + line.product_uom_qty and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): + raise UserError('Product '+line.product_id.name+' melebihi plafon, harus Approval MD') + + def check_different_vendor_so_po(self): + vendor_po = self.partner_id.id + for line in self.order_line: + if not line.so_line_id: + continue + if line.so_line_id.vendor_id.id != vendor_po and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): + raise UserError("Produk "+line.product_id.name+" memiliki vendor berbeda dengan SO (Vendor PO: "+str(self.partner_id.name)+", Vendor SO: "+str(line.so_line_id.vendor_id.name)+")") + def button_confirm(self): res = super(PurchaseOrder, self).button_confirm() current_time = datetime.now() self.check_ppn_mix() + self.check_different_vendor_so_po() # self.check_data_vendor() if self.amount_untaxed >= 50000000 and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): @@ -635,7 +656,8 @@ class PurchaseOrder(models.Model): self.date_planned = delta_time self.date_deadline_ref_date_planned() self.unlink_purchasing_job_state() - + + self._check_qty_plafon_product() return res @@ -723,11 +745,12 @@ class PurchaseOrder(models.Model): template.send_mail(self.id, force_send=True) def po_approve(self): - if self.amount_untaxed >= 50000000 and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): - raise UserError("Hanya Merchandiser yang bisa approve") + greater_than_plafon, message = self._get_msg_plafon_qty() + different_vendor_message = self.check_different_vendor_so() # Panggil fungsi check_different_vendor_so + if self.env.user.is_leader or self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): raise UserError("Bisa langsung Confirm") - elif self.total_percent_margin == self.total_so_percent_margin and self.matches_so: + elif self.total_percent_margin == self.total_so_percent_margin and self.matches_so and not greater_than_plafon and not different_vendor_message: raise UserError("Bisa langsung Confirm") else: reason = '' @@ -740,13 +763,46 @@ class PurchaseOrder(models.Model): reason += 'not link with pj and reorder, ' if not self.matches_so: reason += 'not link with so, ' + # Check Plafon Qty and Get Message every Line Product + if greater_than_plafon: + reason += message + # Check for Different Vendor Message + if different_vendor_message: + reason += different_vendor_message + # Post a highlighted message to lognote self.message_post( body=f"<div style='background-color: #fdf2e9; border: 1px solid #f5c6cb; padding: 10px;'>" - f"<b>Note (Pinned):</b><br>{reason}</div>", + f"<b>Note (Pinned):</b><br>{reason}</div>", subtype_id=self.env.ref("mail.mt_note").id ) + + def check_different_vendor_so(self): + vendor_po = self.partner_id.id + message = '' + for line in self.order_line: + if not line.so_line_id: + continue + if line.so_line_id.vendor_id.id != vendor_po: + product_code = line.product_id.display_name or 'Unknown' + message += (f"Produk {product_code} memiliki vendor berbeda dengan SO " + f"(Vendor PO: {self.partner_id.name}, Vendor SO: {line.so_line_id.vendor_id.name}), ") + return message if message else None + + def _get_msg_plafon_qty(self): + message = '' + greater_than_plafon = False + for line in self.order_line: + if not line.product_id: + continue + if line.product_uom_qty > line.product_id.plafon_qty: + message = (message + '\n'+line.product_id.default_code + ' melebihi plafon (' + + str(line.product_id.plafon_qty) + ') vs Qty PO ('+str(line.product_uom_qty)+')' + + ', ') + greater_than_plafon = True + return greater_than_plafon, message + def re_calculate(self): if self.from_apo: self.re_calculate_from_apo() diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index bf5ed8c4..4efb0cd4 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -57,7 +57,7 @@ class PurchasingJob(models.Model): max(pjs.status_apo::text) AS status_apo, max(pjs.note::text) AS note, CASE - WHEN sub.vendor_id = 5571 THEN 27 + WHEN pmp.brand IN ('Tekiro', 'RYU', 'Rexco') THEN 27 WHEN sub.vendor_id = 9688 THEN 397 WHEN sub.vendor_id = 35475 THEN 397 WHEN sub.vendor_id = 29712 THEN 397 diff --git a/indoteknik_custom/models/report_stock_forecasted.py b/indoteknik_custom/models/report_stock_forecasted.py index ebdc7d4a..c9d54a15 100644 --- a/indoteknik_custom/models/report_stock_forecasted.py +++ b/indoteknik_custom/models/report_stock_forecasted.py @@ -6,33 +6,33 @@ class ReplenishmentReport(models.AbstractModel): @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, - }) + # 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): diff --git a/indoteknik_custom/models/requisition.py b/indoteknik_custom/models/requisition.py index c4104ec5..32a9f94f 100644 --- a/indoteknik_custom/models/requisition.py +++ b/indoteknik_custom/models/requisition.py @@ -1,4 +1,4 @@ -from odoo import models, fields, api, _ +from odoo import models, fields, api, tools, _ from odoo.exceptions import UserError from datetime import datetime import math @@ -7,6 +7,33 @@ import logging _logger = logging.getLogger(__name__) +class RequisitionMatchPO(models.Model): + _name = 'v.requisition.match.po' + _auto = False + _rec_name = 'product_id' + + id = fields.Integer(string='ID') + product_id = fields.Many2one('product.product', string='Product') + qty_rpo = fields.Float(string='Qty RPO', help='Qty RPO yang sudah di PO namun SO masih Draft') + + def init(self): + tools.drop_view_if_exists(self.env.cr, self._table) + self.env.cr.execute(""" + create or replace view %s as + select rl.product_id as id, rl.product_id, sum(rl.qty_purchase) as qty_rpo + from requisition_line rl + join requisition r on r.id = rl.requisition_id + join requisition_purchase_match rpm on rpm.requisition_id = r.id + join purchase_order po on po.id = rpm.order_id + join sale_order so on so.id = r.sale_order_id + where 1=1 + and r.date_doc >= '2024-11-11' + and po.state in ('done', 'purchase') + and so.state in ('draft', 'sent') + group by rl.product_id + """ % self._table) + + class Requisition(models.Model): _name = 'requisition' _order = 'id desc' @@ -20,21 +47,61 @@ class Requisition(models.Model): notification = fields.Char(string='Notification') is_po = fields.Boolean(string='Is PO') requisition_match = fields.One2many('requisition.purchase.match', 'requisition_id', string='Matches', auto_join=True) - sale_order_id = fields.Many2one('sale.order', string='SO', help='harus diisi nomor SO yang ingin digenerate', - domain="[('state', '=', 'sale')]") + sale_order_id = fields.Many2one('sale.order', string='SO', help='harus diisi nomor SO yang ingin digenerate') + sales_approve = fields.Boolean(string='Sales Approve', tracking=3, copy=False) + merchandise_approve = fields.Boolean(string='Merchandise Approve', tracking=3, copy=False) + + def generate_requisition_from_so(self): + state = ['done', 'sale'] + if not self.sale_order_id: + raise UserError('Sale Order Wajib Diisi dan Harus Draft') + if self.sale_order_id.state in state: + raise UserError('SO sudah Confirm, akan berakibat double Purchase melalui PJ') + if not self.sale_order_id.order_line: + raise UserError('Line SO masih kosong, harus diisi dulu') + for order_line in self.sale_order_id.order_line: + param = { + 'requisition_id': self.id, + 'product_id': order_line.product_id.id, + 'partner_id': order_line.vendor_id.id, + 'qty_purchase': order_line.product_uom_qty, + 'price_unit': order_line.purchase_price, + 'taxes_id': order_line.purchase_tax_id.id, + 'subtotal': order_line.purchase_price * order_line.product_uom_qty, + 'brand_id': order_line.product_id.x_manufacture.id + } + self.env['requisition.line'].create([param]) @api.model def create(self, vals): vals['number'] = self.env['ir.sequence'].next_by_code('requisition') or '0' result = super(Requisition, self).create(vals) return result - + + def button_approve(self): + state = ['done', 'sale'] + if self.sale_order_id.state in state: + raise UserError('SO sudah Confirm, akan berakibat double Purchase melalui PJ') + if self.env.user.id not in [377, 19]: + raise UserError('Hanya Vita dan Darren Yang Bisa Approve') + if self.env.user.id == 377: + self.sales_approve = True + elif self.env.user.id == 19: + if not self.sales_approve: + raise UserError('Vita Belum Approve') + self.merchandise_approve = True def create_po_from_requisition(self): + if not self.sales_approve: + raise UserError('Harus Di Approve oleh Vita') + if not self.merchandise_approve: + raise UserError('Harus Di Approve oleh Darren') if not self.requisition_lines: raise UserError('Tidak ada Lines, belum bisa create PO') if self.is_po: raise UserError('Sudah pernah di create PO') + if not self.sale_order_id: + raise UserError('Tidak ada link dengan Sales Order, tidak bisa dihitung sebagai Plafon Qty di PO') vendor_ids = self.env['requisition.line'].read_group([ ('requisition_id', '=', self.id), @@ -101,13 +168,20 @@ class Requisition(models.Model): 'product_id': product.id, 'product_qty': line.qty_purchase, 'product_uom_qty': line.qty_purchase, - 'name': product.name, + 'name': product.display_name, 'price_unit': line.price_unit, 'taxes_id': tax, } 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.env['requisition.purchase.match'].create([{ + 'requisition_id': self.id, + 'order_id': new_po.id + }]) + self.is_po = True + return po_ids # def create_po_from_requisition(self): diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index b6427745..da4a6cb6 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -10,6 +10,16 @@ class GroupPartner(models.Model): class ResPartner(models.Model): _inherit = 'res.partner' + property_account_payable_id = fields.Many2one('account.account', company_dependent=True, + string="Account Payable", + domain="[('internal_type', '=', 'payable'), ('deprecated', '=', False), ('company_id', '=', current_company_id)]", + help="This account will be used instead of the default one as the payable account for the current partner", + default=438) + property_account_receivable_id = fields.Many2one('account.account', company_dependent=True, + string="Account Receivable", + domain="[('internal_type', '=', 'receivable'), ('deprecated', '=', False), ('company_id', '=', current_company_id)]", + help="This account will be used instead of the default one as the receivable account for the current partner", + default=395) reference_number = fields.Char(string="Reference Number") company_type_id = fields.Many2one('res.partner.company_type', string='Company Type') custom_pricelist_id = fields.Many2one('product.pricelist', string='Price Matrix') @@ -23,7 +33,7 @@ class ResPartner(models.Model): counter = fields.Integer(string="Counter", default=0) leadtime = fields.Integer(string="Leadtime", default=0) digital_invoice_tax = fields.Boolean(string="Digital Invoice & Faktur Pajak") - is_potential = fields.Boolean(string='Potential') + is_not_potential = fields.Boolean(string='Not Potential') pakta_integritas = fields.Boolean(string='Pakta Integritas') use_so_approval = fields.Boolean(string='Use SO Approval') @@ -54,6 +64,16 @@ class ResPartner(models.Model): company_type = fields.Selection(string='Company Type', selection=[('person', 'Individual'), ('company', 'Company')], compute='_compute_company_type', inverse='_write_company_type', tracking=3) + warning_stage = fields.Float(string='Warning Amount', + help="A warning message will appear once the " + "selected customer is crossed warning " + "amount. Set its value to 0.00 to" + " disable this feature", tracking=3) + blocking_stage = fields.Float(string='Blocking Amount', + help="Cannot make sales once the selected " + "customer is crossed blocking amount." + "Set its value to 0.00 to disable " + "this feature", tracking=3) @api.model def _default_payment_term(self): diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index c00b2f4e..7fc6d96a 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -11,6 +11,7 @@ _logger = logging.getLogger(__name__) class SaleOrder(models.Model): _inherit = "sale.order" + fulfillment_line_v2 = fields.One2many('sales.order.fulfillment.v2', 'sale_order_id', string='Fullfillment2') fullfillment_line = fields.One2many('sales.order.fullfillment', 'sales_order_id', string='Fullfillment') reject_line = fields.One2many('sales.order.reject', 'sale_order_id', string='Reject Lines') order_sales_match_line = fields.One2many('sales.order.purchase.match', 'sales_order_id', string='Purchase Match Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True) @@ -78,7 +79,7 @@ class SaleOrder(models.Model): payment_link_midtrans = fields.Char(string='Payment Link', help='Url payment yg digenerate oleh midtrans, harap diserahkan ke customer agar dapat dilakukan pembayaran secara mandiri') payment_qr_code = fields.Binary("Payment QR Code") due_id = fields.Many2one('due.extension', string="Due Extension", readonly=True, tracking=True) - vendor_approval_id = fields.Many2one('vendor.approval', string="Vendor Approval", readonly=True, tracking=True) + vendor_approval_id = fields.Many2many('vendor.approval', string="Vendor Approval", readonly=True, tracking=True, copy=False) customer_type = fields.Selection([ ('pkp', 'PKP'), ('nonpkp', 'Non PKP') @@ -157,7 +158,7 @@ class SaleOrder(models.Model): missing_weight_products = [] for line in self.order_line: - if line.weight: + if line.weight > 0: total_weight += line.weight * line.product_uom_qty self.total_weight = total_weight @@ -170,7 +171,7 @@ class SaleOrder(models.Model): missing_weight_products = [] for line in self.order_line: - if line.weight: + if line.weight > 0: total_weight += line.weight * line.product_uom_qty line.product_id.weight = line.weight else: @@ -195,7 +196,7 @@ class SaleOrder(models.Model): missing_weight_products = [] for line in self.order_line: - if line.weight: + if line.weight > 0: total_weight += line.weight * line.product_uom_qty line.product_id.weight = line.weight else: @@ -355,10 +356,10 @@ class SaleOrder(models.Model): def _compute_fullfillment(self): for rec in self: - rec.fullfillment_line.unlink() - - for line in rec.order_line: - line._compute_reserved_from() + # rec.fullfillment_line.unlink() + # + # for line in rec.order_line: + # line._compute_reserved_from() rec.compute_fullfillment = True @@ -499,8 +500,8 @@ class SaleOrder(models.Model): raise UserError('Phone Real Delivery Address harus diisi') if not real_delivery_address.kecamatan_id: raise UserError('Kecamatan Real Delivery Address harus diisi') - if not real_delivery_address.kelurahan_id: - raise UserError('Kelurahan Real Delivery Address harus diisi') + # if not real_delivery_address.kelurahan_id: + # raise UserError('Kelurahan Real Delivery Address harus diisi') def generate_payment_link_midtrans_sales_order(self): # midtrans_url = 'https://app.sandbox.midtrans.com/snap/v1/transactions' # dev - sandbox @@ -634,20 +635,6 @@ class SaleOrder(models.Model): # return ['&', ('order_line.invoice_lines.move_id.move_type', 'in', ('out_invoice', 'out_refund')), ('order_line.invoice_lines.move_id', operator, value)] - def check_data_real_delivery_address(self): - real_delivery_address = self.real_shipping_id - - if not real_delivery_address.state_id: - raise UserError('State Real Delivery Address harus diisi') - if not real_delivery_address.zip: - raise UserError('Zip code Real Delivery Address harus diisi') - if not real_delivery_address.mobile: - raise UserError('Mobile Real Delivery Address harus diisi') - if not real_delivery_address.phone: - raise UserError('Phone Real Delivery Address harus diisi') - if not real_delivery_address.kecamatan_id: - raise UserError('Kecamatan Real Delivery Address harus diisi') - @api.onchange('partner_id') def onchange_partner_contact(self): parent_id = self.partner_id.parent_id @@ -658,6 +645,7 @@ class SaleOrder(models.Model): self.customer_type = parent_id.customer_type self.email = parent_id.email self.pareto_status = parent_id.pareto_status + self.user_id = parent_id.user_id @api.onchange('partner_id') def onchange_partner_id(self): @@ -770,7 +758,7 @@ class SaleOrder(models.Model): raise UserError("Salesperson sudah tidak aktif, mohon diisi yang benar pada data SO dan Contact") def sale_order_approve(self): - if self.validate_different_vendor() and not self.vendor_approval and not self.vendor_approval_id: + 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() @@ -903,29 +891,26 @@ class SaleOrder(models.Model): }).send() def validate_different_vendor(self): - if self.vendor_approval_id and self.vendor_approval_id.state == 'draft': - raise UserError('SO ini sedang dalam review Vendor Approval') - - if self.vendor_approval_id and self.vendor_approval_id.state == 'cancel': - raise UserError('Vendor Approval SO ini Di Reject') - - if self.vendor_approval_id and self.vendor_approval_id.state == 'done': + 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')) + raise UserError(f"SO ini sedang dalam review Vendor Approval: {draft_names}") + + if self.vendor_approval_id and all(v.state != 'draft' for v in self.vendor_approval_id): return False - different_vendor = self.order_line.filtered(lambda l: l.vendor_id and l.vendor_md_id and l.vendor_id.id != l.vendor_md_id.id) + different_vendor = self.order_line.filtered( + lambda l: l.vendor_id and l.vendor_md_id and l.vendor_id.id != l.vendor_md_id.id + ) + if different_vendor: - vendor_approval = self.env['vendor.approval'].create({ - 'order_id': self.id, - 'create_date_so': self.create_date, - 'partner_id': self.partner_id.id, - 'state': 'draft', - }) - - self.vendor_approval_id = vendor_approval.id - + vendor_approvals = [] for line in different_vendor: - self.env['vendor.approval.line'].create({ - 'vendor_approval_id': vendor_approval.id, + vendor_approval = self.env['vendor.approval'].create({ + 'order_id': self.id, + 'order_line_id': line.id, + 'create_date_so': self.create_date, + 'partner_id': self.partner_id.id, + 'state': 'draft', 'product_id': line.product_id.id, 'product_uom_qty': line.product_uom_qty, 'vendor_id': line.vendor_id.id, @@ -937,9 +922,15 @@ class SaleOrder(models.Model): 'margin_after': line.item_percent_margin, 'purchase_tax_id': line.purchase_tax_id.id, 'sales_tax_id': line.tax_id[0].id if line.tax_id else False, - 'percent_margin_difference': (line.price_unit - line.purchase_price_md) / line.purchase_price_md if line.purchase_price_md else False, + 'percent_margin_difference': ( + (line.price_unit - line.purchase_price_md) / line.purchase_price_md + if line.purchase_price_md else False + ), }) - + + vendor_approvals.append(vendor_approval.id) + + self.vendor_approval_id = [(4, vid) for vid in vendor_approvals] return True else: return False @@ -947,7 +938,7 @@ class SaleOrder(models.Model): def action_confirm(self): for order in self: - if self.validate_different_vendor() and not self.vendor_approval and not self.vendor_approval_id: + if self.validate_different_vendor() and not self.vendor_approval: return self._create_notification_action('Notification', 'Terdapat Vendor yang berbeda dengan MD Vendor') order.check_data_real_delivery_address() @@ -988,6 +979,16 @@ class SaleOrder(models.Model): # order.order_line.get_reserved_from() res = super(SaleOrder, self).action_confirm() + for order in self: + note = [] + for line in order.order_line: + if line.display_type == 'line_note': + note.append(line.name) + + if order.picking_ids: + # Sort picking_ids by creation date to get the most recent one + latest_picking = order.picking_ids.sorted(key=lambda p: p.create_date, reverse=True)[0] + latest_picking.notee = '\n'.join(note) return res def action_cancel(self): @@ -1071,6 +1072,8 @@ class SaleOrder(models.Model): partner.email = self.email if not partner.customer_type: partner.customer_type = self.customer_type + if not partner.user_id: + partner.user_id = self.user_id.id # if not partner.sppkp or not partner.npwp or not partner.email or partner.customer_type: # partner.customer_type = self.customer_type diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 5e01067a..04fafa69 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -31,11 +31,17 @@ class SaleOrderLine(models.Model): vendor_subtotal = fields.Float(string='Vendor Subtotal', compute="_compute_vendor_subtotal") amount_voucher_disc = fields.Float(string='Voucher Discount') qty_reserved = fields.Float(string='Qty Reserved', compute='_compute_qty_reserved') + product_available_quantity = fields.Float(string='Qty pickup by user',) reserved_from = fields.Char(string='Reserved From', copy=False) item_percent_margin_without_deduction = fields.Float('%Margin', compute='_compute_item_margin_without_deduction') weight = fields.Float(string='Weight') 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') + + def _get_qty_free_bandengan(self): + for line in self: + line.qty_free_bu = line.product_id.qty_free_bandengan @api.constrains('note_procurement') def note_procurement_to_apo(self): @@ -251,12 +257,9 @@ class SaleOrderLine(models.Model): # purchase_price = self.env['purchase.pricelist'].search( # query, limit=1, order='count_trx_po desc, count_trx_po_vendor desc') price, taxes, vendor_id = self._get_purchase_price(line.product_id) - line.vendor_md_id = vendor_id line.vendor_id = vendor_id - line.margin_md = line.item_percent_margin line.tax_id = line.order_id.sales_tax_id # price, taxes = line._get_valid_purchase_price(purchase_price) - line.purchase_price_md = price line.purchase_price = price line.purchase_tax_id = taxes @@ -270,6 +273,14 @@ class SaleOrderLine(models.Model): line.name = line_name line.weight = line.product_id.weight + @api.constrains('vendor_id') + def _check_vendor_id(self): + for line in self: + price, taxes, vendor_id = self._get_purchase_price(line.product_id) + line.vendor_md_id = vendor_id if vendor_id else None + line.margin_md = line.item_percent_margin + line.purchase_price_md = price + def compute_delivery_amt_line(self): for line in self: try: @@ -366,5 +377,5 @@ class SaleOrderLine(models.Model): continue if not line.product_id.product_tmpl_id.sale_ok: raise UserError('Product %s belum bisa dijual, harap hubungi finance' % line.product_id.display_name) - if not line.vendor_id or not line.purchase_price: + if not line.vendor_id or not line.purchase_price and not line.display_type == 'line_note': raise UserError(_('Isi Vendor dan Harga Beli sebelum Request Approval'))
\ No newline at end of file diff --git a/indoteknik_custom/models/sales_order_fullfillment.py b/indoteknik_custom/models/sales_order_fullfillment.py index ab416e8d..05a0641c 100644 --- a/indoteknik_custom/models/sales_order_fullfillment.py +++ b/indoteknik_custom/models/sales_order_fullfillment.py @@ -6,6 +6,24 @@ import logging _logger = logging.getLogger(__name__) +class SalesOrderFullfillmentV2(models.Model): + _name = 'sales.order.fulfillment.v2' + + sale_order_id = fields.Many2one('sale.order', string='Sale Order') + sale_order_line_id = fields.Many2one('sale.order.line', string='Sale Order Line') + picking_id = fields.Many2one('stock.picking', string='Picking') + move_id = fields.Many2one('stock.move', string='Move') + move_line_id = fields.Many2one('stock.move.line', string='Move Line') + product_id = fields.Many2one('product.product', string='Product') + so_qty = fields.Float(string='SO Qty') + reserved_stock_qty = fields.Float(string='Reserved Stock Qty', help='Sudah ter-reserved oleh sistem') + delivered_qty = fields.Float(string='Delivered Qty', help='Yang sudah terkirim ke Customer') + po_ids = fields.Many2many('purchase.order', string='Purchase Order', help='PO yang dibuat, bisa lebih dari satu') + po_qty = fields.Float(string='PO Qty', help='Totalan dari semua PO Outstanding') + received_qty = fields.Float(string='Received Qty', help='Totalan dari barang yang diterima dari PO tsb') + purchaser = fields.Char(string='Purchaser') + + class SalesOrderFullfillment(models.Model): _name = 'sales.order.fullfillment' diff --git a/indoteknik_custom/models/solr/product_product.py b/indoteknik_custom/models/solr/product_product.py index dd1d40f6..667511b2 100644 --- a/indoteknik_custom/models/solr/product_product.py +++ b/indoteknik_custom/models/solr/product_product.py @@ -54,7 +54,7 @@ class ProductProduct(models.Model): ('location_id', 'in', target_locations), ]) - is_in_bu = any(quant.available_quantity > 0 for quant in stock_quant) + is_in_bu = True if variant.qty_free_bandengan > 0 else False document = solr_model.get_doc('variants', variant.id) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a4031d52..17dd5766 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -34,7 +34,6 @@ class StockPicking(models.Model): ) driver_arrival_date = fields.Datetime( string='Delivery Arrival Date', - readonly=True, copy=False ) delivery_tracking_no = fields.Char( @@ -104,6 +103,7 @@ class StockPicking(models.Model): ('to invoice', 'To Invoice'), ('no', 'Nothing to Invoice') ], string='Invoice Status', related="sale_id.invoice_status") + note_return = fields.Text(string="Note Return", help="Catatan untuk kirim barang kembali") state_reserve = fields.Selection([ ('waiting', 'Waiting For Fullfilment'), @@ -111,6 +111,7 @@ class StockPicking(models.Model): ('done', 'Done'), ('cancel', 'Cancelled'), ], string='Status Reserve', readonly=True, tracking=True, help="The current state of the stock picking.") + notee = fields.Text(string="Note") def action_send_to_biteship(self): url = "https://api.biteship.com/v1/orders" @@ -192,9 +193,10 @@ class StockPicking(models.Model): else: raise UserError(f"Error saat mengirim ke Biteship: {response.content}") - # @api.constrains('driver_departure_date') - # def constrains_driver_departure_date(self): - # self.date_doc_kirim = self.driver_departure_date + @api.constrains('driver_departure_date') + def constrains_driver_departure_date(self): + if not self.date_doc_kirim: + self.date_doc_kirim = self.driver_departure_date @api.constrains('arrival_time') def constrains_arrival_time(self): @@ -246,25 +248,22 @@ class StockPicking(models.Model): # break def check_state_reserve(self): - picking = self.search([ + pickings = self.search([ ('state', 'not in', ['cancel', 'draft', 'done']), ('picking_type_code', '=', 'outgoing') - ]) + ]) - for data in picking: - fullfilment = self.env['sales.order.fullfillment'].search([ - ('sales_order_id', '=', data.sale_id.id) + for picking in pickings: + fullfillments = self.env['sales.order.fullfillment'].search([ + ('sales_order_id', '=', picking.sale_id.id) ]) - - data.state_reserve = 'ready' - if not data.date_reserved: - data.date_reserved = datetime.datetime.utcnow() - - for rec in fullfilment: - if rec.reserved_from not in ['Inventory On Hand', 'Reserved from stock', 'Free Stock']: - data.state_reserve = 'waiting' - data.date_reserved = '' - break + + 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): + picking.state_reserve = 'waiting' + picking.date_reserved = '' def _create_approval_notification(self, approval_role): title = 'Warning' @@ -281,9 +280,9 @@ class StockPicking(models.Model): def _compute_shipping_status(self): for rec in self: status = 'pending' - if rec.driver_departure_date and not rec.driver_arrival_date: + if rec.driver_departure_date and not (rec.sj_return_date or rec.driver_arrival_date): status = 'shipment' - elif rec.driver_departure_date and rec.driver_arrival_date: + elif rec.driver_departure_date and (rec.sj_return_date or rec.driver_arrival_date): status = 'completed' rec.shipping_status = status @@ -443,10 +442,34 @@ class StockPicking(models.Model): for pick in self: if self.env.user.is_accounting: pick.approval_return_status = 'approved' - else: - pick.approval_return_status = 'pengajuan1' + continue + + action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_stock_return_note_wizard') + + if self.picking_type_code == 'outgoing': + if self.env.user.id in [3988, 3401, 20] or ( + self.env.user.has_group('indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin + ): + action['context'] = {'picking_ids': [x.id for x in self]} + return action + elif not self.env.user.has_group('indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin: + raise UserError('Harus Purchasing yang Ask Return') + else: + raise UserError('Harus Sales Admin yang Ask Return') + + elif self.picking_type_code == 'incoming': + if self.env.user.has_group('indoteknik_custom.group_role_purchasing') or ( + self.env.user.id in [3988, 3401, 20] and 'Return of' in self.origin + ): + action['context'] = {'picking_ids': [x.id for x in self]} + return action + elif not self.env.user.id in [3988, 3401, 20] and 'Return of' in self.origin: + raise UserError('Harus Sales Admin yang Ask Return') + else: + raise UserError('Harus Purchasing yang Ask Return') def calculate_line_no(self): + for picking in self: name = picking.group_id.name for move in picking.move_ids_without_package: @@ -500,9 +523,32 @@ class StockPicking(models.Model): and quant.inventory_quantity < line.product_uom_qty ): raise UserError('Quantity reserved lebih besar dari quantity onhand di product') + + def check_qty_done_stock(self): + for line in self.move_line_ids_without_package: + def check_qty_per_inventory(self, product, location): + quant = self.env['stock.quant'].search([ + ('product_id', '=', product.id), + ('location_id', '=', location.id), + ]) + + if quant: + return quant.quantity + return 0 + + qty_onhand = check_qty_per_inventory(self, line.product_id, line.location_id) + if line.qty_done > qty_onhand: + raise UserError('Quantity Done melebihi Quantity Onhand') def button_validate(self): + if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': + if self.origin and 'Return of' in self.origin: + raise UserError("Button ini hanya untuk Logistik") + + if self.picking_type_code == 'internal': + self.check_qty_done_stock() + if self._name != 'stock.picking': return super(StockPicking, self).button_validate() @@ -524,10 +570,10 @@ class StockPicking(models.Model): if self.location_dest_id.id == 47 and not self.env.user.is_purchasing_manager: raise UserError("Transfer ke gudang selisih harus di approve Rafly Hanggara") - if self.group_id.sale_id: - if self.group_id.sale_id.payment_link_midtrans: - if self.group_id.sale_id.payment_status != 'settlement': - raise UserError('Uang belum masuk (settlement), mohon konfirmasi ke sales atau finance') + # if self.group_id.sale_id: + # if self.group_id.sale_id.payment_link_midtrans: + # if self.group_id.sale_id.payment_status != 'settlement' and self.group_id.sale_id.state == 'draft': + # raise UserError('Uang belum masuk (settlement), mohon konfirmasi ke sales atau finance') if self.is_internal_use: self.approval_status = 'approved' @@ -553,6 +599,14 @@ class StockPicking(models.Model): self.date_done = datetime.datetime.utcnow() self.state_reserve = 'done' return res + def action_cancel(self): + if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking': + if self.origin and 'Return of' in self.origin: + raise UserError("Button ini hanya untuk Logistik") + + res = super(StockPicking, self).action_cancel() + return res + @api.model def create(self, vals): @@ -622,13 +676,13 @@ class StockPicking(models.Model): manifest_datas = [] departure_date = self.driver_departure_date - arrival_date = self.driver_arrival_date + arrival_date = self.sj_return_date if self.sj_return_date else self.driver_arrival_date status = status_mapping.get(status_key) if not status: return manifest_datas - if arrival_date: + if arrival_date or self.sj_return_date: manifest_datas.append(self.create_manifest_data(status['arrival'], arrival_date)) if departure_date: manifest_datas.append(self.create_manifest_data(status['departure'], departure_date)) @@ -655,7 +709,7 @@ class StockPicking(models.Model): } if not self.waybill_id or len(self.waybill_id.manifest_ids) == 0: - response['delivered'] = self.driver_arrival_date != False + response['delivered'] = self.sj_return_date != False or self.driver_arrival_date != False return response response['delivery_order']['receiver_name'] = self.waybill_id.receiver_name @@ -688,4 +742,4 @@ class StockPicking(models.Model): formatted_fastest_eta = fastest_eta.strftime(format_time_fastest) formatted_longest_eta = longest_eta.strftime(format_time) - return f'{formatted_fastest_eta} - {formatted_longest_eta}' + return f'{formatted_fastest_eta} - {formatted_longest_eta}'
\ No newline at end of file diff --git a/indoteknik_custom/models/user_company_request.py b/indoteknik_custom/models/user_company_request.py index 64e11700..dd9a35c1 100644 --- a/indoteknik_custom/models/user_company_request.py +++ b/indoteknik_custom/models/user_company_request.py @@ -74,7 +74,7 @@ class UserCompanyRequest(models.Model): if not self.is_approve and is_approve: if is_approve == 'approved': - self.user_id.parent_id = self.user_company_id.id if self.user_company_id.id else vals.get('user_company_id') + self.user_id.parent_id = vals.get('user_company_id') if vals.get('user_company_id') else self.user_company_id.id self.user_id.customer_type = self.user_company_id.customer_type self.user_id.npwp = self.user_company_id.npwp self.user_id.sppkp = self.user_company_id.sppkp diff --git a/indoteknik_custom/models/va_multi_approve.py b/indoteknik_custom/models/va_multi_approve.py new file mode 100644 index 00000000..028358c2 --- /dev/null +++ b/indoteknik_custom/models/va_multi_approve.py @@ -0,0 +1,22 @@ +from odoo import models, fields +import logging + +_logger = logging.getLogger(__name__) + + +class VaMultiApprove(models.TransientModel): + _name = 'va.multi.approve' + + def save_multi_approve_va(self): + va_ids = self._context['va_ids'] + vendor_approval = self.env['vendor.approval'].browse(va_ids) + vendor_approval.action_approve() + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Notification', + 'message': 'Berhasil Di Approve', + 'next': {'type': 'ir.actions.act_window_close'}, + } + }
\ No newline at end of file diff --git a/indoteknik_custom/models/va_multi_reject.py b/indoteknik_custom/models/va_multi_reject.py new file mode 100644 index 00000000..c05581bf --- /dev/null +++ b/indoteknik_custom/models/va_multi_reject.py @@ -0,0 +1,22 @@ +from odoo import models, fields +import logging + +_logger = logging.getLogger(__name__) + + +class VaMultiReject(models.TransientModel): + _name = 'va.multi.reject' + + def save_multi_reject_va(self): + va_ids = self._context['va_ids'] + vendor_approval = self.env['vendor.approval'].browse(va_ids) + vendor_approval.action_reject() + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Notification', + 'message': 'Berhasil Di Reject', + 'next': {'type': 'ir.actions.act_window_close'}, + } + }
\ No newline at end of file diff --git a/indoteknik_custom/models/vendor_approval.py b/indoteknik_custom/models/vendor_approval.py index b0d58b85..01d2e6a2 100644 --- a/indoteknik_custom/models/vendor_approval.py +++ b/indoteknik_custom/models/vendor_approval.py @@ -13,61 +13,90 @@ class VendorApproval(models.Model): number = fields.Char(string='Document No', index=True, copy=False, readonly=True, tracking=True) partner_id = fields.Many2one('res.partner', string="Customer", readonly=True) + order_line_id = fields.Many2one('sale.order.line', string="SO Line", readonly=True) order_id = fields.Many2one('sale.order', string="SO", readonly=True) - vendor_approval_line = fields.One2many('vendor.approval.line', 'vendor_approval_id', string='Vendor Approval Lines', auto_join=True) state = fields.Selection([('draft', 'Draft'), ('done', 'Done'), ('cancel', 'Reject')], string='State', tracking=True) create_date_so = fields.Datetime(string='Create Date SO', readonly=True) + product_id = fields.Many2one('product.product', string='Product') + product_uom_qty = fields.Float(string='Quantity') + vendor_id = fields.Many2one('res.partner', string='Vendor') + vendor_md_id = fields.Many2one('res.partner', string='Vendor MD') + sales_price = fields.Float(string='Sales Price') + margin_before = fields.Float(string='Margin Before') + margin_after = fields.Float(string='Margin After') + purchase_price = fields.Float(string='Purchase Price') + purchase_price_md= fields.Float(string='Purchase Price MD') + purchase_tax_id = fields.Many2one('account.tax', string='Purchase Tax', domain=['|', ('active', '=', False), ('active', '=', True)]) + sales_tax_id = fields.Many2one('account.tax', string='Sales Tax', domain=['|', ('active', '=', False), ('active', '=', True)]) + percent_margin_difference = fields.Float(string='Percent Margin Difference') @api.model def create(self, vals): vals['number'] = self.env['ir.sequence'].next_by_code('vendor.approval') or '0' result = super(VendorApproval, self).create(vals) return result + + def check_state_so(self): + for rec in self: + if rec.order_id.state != 'draft': + raise UserError(f"SO {rec.order_id.name} sudah tidak bisa diubah") + def action_approve(self): - if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): - raise UserError('Hanya Merchandiser yang bisa approve') - - self.state = 'done' - self.order_id.vendor_approval = True - self.order_id.action_confirm() - message = "Vendor Approval approved by %s" % (self.env.user.name) - self.order_id.message_post(body=message) + for rec in self: + self.check_state_so() + if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): + raise UserError('Hanya Merchandiser yang bisa approve') + + # Set state menjadi 'done' + rec.state = 'done' + rec.order_id.vendor_approval = True + message = f"Vendor Approval approved by {self.env.user.name}" + rec.order_id.message_post(body=message) + + # Cek semua vendor.approval dengan order_id yang sama + related_approvals = self.env['vendor.approval'].search([('order_id', '=', rec.order_id.id)]) + if all(approval.state != 'draft' for approval in related_approvals): + # Jalankan action_confirm hanya jika semua state bukan draft + if not rec.order_id.due_id and rec.order_id.state == 'draft': + rec.order_id.action_confirm() def action_reject(self): - if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): - raise UserError('Hanya Merchandiser yang bisa cancel') - - self.state = 'cancel' + for rec in self: + self.check_state_so() + if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): + raise UserError('Hanya Merchandiser yang bisa cancel') + + rec.state = 'cancel' + + rec.order_line_id.vendor_id = rec.vendor_md_id.id + rec.order_line_id.purchase_price = rec.purchase_price_md + + message = "Vendor Approval rejected by %s" % (self.env.user.name) + self.order_id.message_post(body=message) - message = "Vendor Approval rejected by %s" % (self.env.user.name) - self.order_id.message_post(body=message) + related_approvals = self.env['vendor.approval'].search([('order_id', '=', rec.order_id.id)]) + if all(approval.state != 'draft' for approval in related_approvals): + # Jalankan action_confirm hanya jika semua state bukan draft + if not rec.order_id.due_id and rec.order_id.state == 'draft': + rec.order_id.action_confirm() def unlink(self): res = super(VendorApproval, self).unlink() if not self._name == 'vendor.approval': raise UserError('Vendor Approval tidak bisa didelete') return res - -class VendorApprovalLine(models.Model): - _name = 'vendor.approval.line' - _description = 'Vendor Approval Line' - _order = 'vendor_approval_id, id' - - vendor_approval_id = fields.Many2one('vendor.approval', string='Vendor Approval Ref', required=True, ondelete='cascade', index=True, copy=False) - product_id = fields.Many2one('product.product', string='Product') - product_uom_qty = fields.Float(string='Quantity') - vendor_id = fields.Many2one('res.partner', string='Vendor') - vendor_md_id = fields.Many2one('res.partner', string='Vendor MD') - sales_price = fields.Float(string='Sales Price') - margin_before = fields.Float(string='Margin Before') - margin_after = fields.Float(string='Margin After') - purchase_price = fields.Float(string='Purchase Price') - purchase_price_md= fields.Float(string='Purchase Price MD') - purchase_tax_id = fields.Many2one('account.tax', string='Purchase Tax', domain=['|', ('active', '=', False), ('active', '=', True)]) - sales_tax_id = fields.Many2one('account.tax', string='Sales Tax', domain=['|', ('active', '=', False), ('active', '=', True)]) - percent_margin_difference = fields.Float(string='Percent Margin Difference') - - + def open_form_multi_approve(self): + action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_va_multi_approve') + action['context'] = { + 'va_ids': [x.id for x in self] + } + return action + def open_form_multi_reject(self): + action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_va_multi_reject') + action['context'] = { + 'va_ids': [x.id for x in self] + } + return action
\ No newline at end of file diff --git a/indoteknik_custom/models/wati.py b/indoteknik_custom/models/wati.py index eed5413e..f3632334 100644 --- a/indoteknik_custom/models/wati.py +++ b/indoteknik_custom/models/wati.py @@ -58,41 +58,56 @@ class WatiNotification(models.Model): return def _create_wati_history_header(self, ticket_id, sender_name, notification_json, date_wati): + # Helper function to remove NUL characters + def remove_null_characters(value): + if isinstance(value, str): + return value.replace('\0', '') + return value + + # Sanitize the input data param_header = { 'ticket_id': ticket_id, - 'conversation_id': notification_json['conversationId'], - 'sender_name': sender_name, - 'wa_id': notification_json['waId'], - 'text': notification_json['text'], - 'date_wati': date_wati or '', + 'conversation_id': remove_null_characters(notification_json.get('conversationId', '')), + 'sender_name': remove_null_characters(sender_name), + 'wa_id': remove_null_characters(notification_json.get('waId', '')), + 'text': remove_null_characters(notification_json.get('text', '')), + 'date_wati': remove_null_characters(date_wati or ''), } + + # Create the record new_header = self.env['wati.history'].create([param_header]) return new_header def _create_wati_history_line(self, new_header, ticket_id, sender_name, notification_json, date_wati): - text_body = notification_json['text'] or '' + # Helper function to remove NUL characters + def remove_null_characters(value): + if isinstance(value, str): + return value.replace('\0', '') + return value + + # Sanitize the input data param_line = { "wati_history_id": new_header.id, - "conversation_id": notification_json['conversationId'], - "data": notification_json['data'] or '', - "event_type": notification_json['eventType'] or '', - # "list_reply": notification_json['listReply'] or '', - # "message_contact": notification_json['messageContact'] or '', - "operator_email": notification_json['operatorEmail'] or '', - "operator_name": notification_json['operatorName'] or '', - "sender_name": sender_name or '', - # "source_url": notification_json['sourceUrl'] or '', - "status_string": notification_json['statusString'] or '', - "text": text_body, + "conversation_id": remove_null_characters(notification_json.get('conversationId', '')), + "data": remove_null_characters(notification_json.get('data', '')), + "event_type": remove_null_characters(notification_json.get('eventType', '')), + "operator_email": remove_null_characters(notification_json.get('operatorEmail', '')), + "operator_name": remove_null_characters(notification_json.get('operatorName', '')), + "sender_name": remove_null_characters(sender_name or ''), + "status_string": remove_null_characters(notification_json.get('statusString', '')), + "text": remove_null_characters(notification_json.get('text', '')), "ticket_id": ticket_id, - "type": notification_json['type'] or '', - "wa_id": notification_json['waId'] or '', - "date_wati": date_wati or '', + "type": remove_null_characters(notification_json.get('type', '')), + "wa_id": remove_null_characters(notification_json.get('waId', '')), + "date_wati": remove_null_characters(date_wati or ''), } + + # Create the record safely without NUL characters self.env['wati.history.line'].create([param_line]) - self._update_header_after_create_line(new_header, sender_name, date_wati, text_body) + self._update_header_after_create_line(new_header, sender_name, date_wati, param_line['text']) return + def _update_header_after_create_line(self, new_header, sender_name, date_wati, text_body): new_header.last_reply_by = sender_name new_header.last_reply_date = date_wati diff --git a/indoteknik_custom/models/website_user_cart.py b/indoteknik_custom/models/website_user_cart.py index 6cb282f8..44393cf1 100644 --- a/indoteknik_custom/models/website_user_cart.py +++ b/indoteknik_custom/models/website_user_cart.py @@ -65,9 +65,11 @@ class WebsiteUserCart(models.Model): if stock_quant: res['is_in_bu'] = True res['on_hand_qty'] = sum(stock_quant.mapped('quantity')) + res['available_quantity'] = sum(stock_quant.mapped('available_quantity')) else: res['is_in_bu'] = False res['on_hand_qty'] = 0 + res['available_quantity'] = 0 flashsales = self.product_id._get_active_flash_sale() res['has_flashsale'] = True if len(flashsales) > 0 else False diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 553047e6..c2ff2a3b 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -139,7 +139,11 @@ access_account_tax,access.account.tax,model_account_tax,,1,1,1,1 access_approval_unreserve,access.approval.unreserve,model_approval_unreserve,,1,1,1,1 access_approval_unreserve_line,access.approval.unreserve.line,model_approval_unreserve_line,,1,1,1,1 access_vendor_approval,access.vendor.approval,model_vendor_approval,,1,1,1,1 -access_vendor_approval_line,access.vendor.approval.line,model_vendor_approval_line,,1,1,1,1 access_vit_kota,access.vit.kota,model_vit_kota,,1,1,1,1 access_v_brand_product_category,access.v.brand.product.category,model_v_brand_product_category,,1,1,1,1 access_web_find_page,access.web.find.page,model_web_find_page,,1,1,1,1 +access_v_requisition_match_po,access.v.requisition.match.po,model_v_requisition_match_po,,1,1,1,1 +access_approval_retur_picking,access.approval.retur.picking,model_approval_retur_picking,,1,1,1,1 +access_sales_order_fulfillment_v2,access.sales.order.fulfillment.v2,model_sales_order_fulfillment_v2,,1,1,1,1 +access_va_multi_approve,access.va.multi.approve,model_va_multi_approve,,1,1,1,1 +access_va_multi_reject,access.va.multi.reject,model_va_multi_reject,,1,1,1,1 diff --git a/indoteknik_custom/views/approval_retur_picking.xml b/indoteknik_custom/views/approval_retur_picking.xml new file mode 100644 index 00000000..5ce28e20 --- /dev/null +++ b/indoteknik_custom/views/approval_retur_picking.xml @@ -0,0 +1,27 @@ +<odoo> + <!-- Form View for Stock Return Note Wizard --> + <record id="view_stock_return_note_form" model="ir.ui.view"> + <field name="name">approval.retur.picking.form</field> + <field name="model">approval.retur.picking</field> + <field name="arch" type="xml"> + <form string="Add Return Note"> + <group> + <span>Ask Approval Retur?</span> + </group> + <footer> + <button name="action_confirm_note_return" string="Confirm" type="object" class="btn-primary"/> + <button string="Cancel" class="btn-secondary" special="cancel"/> + </footer> + </form> + </field> + </record> + + <!-- Action to Open the Wizard --> + <record id="action_stock_return_note_wizard" model="ir.actions.act_window"> + <field name="name">Add Return Note</field> + <field name="res_model">approval.retur.picking</field> + <field name="view_mode">form</field> + <field name="view_id" ref="view_stock_return_note_form"/> + <field name="target">new</field> + </record> +</odoo> diff --git a/indoteknik_custom/views/form_vendor_approval_multi_approve.xml b/indoteknik_custom/views/form_vendor_approval_multi_approve.xml new file mode 100644 index 00000000..d9be107e --- /dev/null +++ b/indoteknik_custom/views/form_vendor_approval_multi_approve.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<odoo> + <data> + <record id="view_va_multi_approve_form" model="ir.ui.view"> + <field name="name">VA Multi Approve</field> + <field name="model">va.multi.approve</field> + <field name="arch" type="xml"> + <form> + <sheet> + <group> + <span>Apakah Anda Yakin Ingin Approve Vendor Approval?</span> + </group> + </sheet> + <footer> + <button name="save_multi_approve_va" string="Update" type="object" default_focus="1" class="oe_highlight"/> + <button string="Cancel" class="btn btn-secondary" special="cancel" /> + </footer> + </form> + </field> + </record> + + <record id="action_va_multi_approve" model="ir.actions.act_window"> + <field name="name">VA Multi Approve</field> + <field name="res_model">va.multi.approve</field> + <field name="type">ir.actions.act_window</field> + <field name="view_mode">form</field> + <field name="view_id" ref="view_va_multi_approve_form"/> + <field name="target">new</field> + </record> + </data> +</odoo>
\ No newline at end of file diff --git a/indoteknik_custom/views/form_vendor_approval_multi_reject.xml b/indoteknik_custom/views/form_vendor_approval_multi_reject.xml new file mode 100644 index 00000000..666de261 --- /dev/null +++ b/indoteknik_custom/views/form_vendor_approval_multi_reject.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<odoo> + <data> + <record id="view_va_multi_reject_form" model="ir.ui.view"> + <field name="name">VA Multi Reject</field> + <field name="model">va.multi.reject</field> + <field name="arch" type="xml"> + <form> + <sheet> + <group> + <span>Apakah Anda Yakin Ingin Reject Vendor Approval?</span> + </group> + </sheet> + <footer> + <button name="save_multi_reject_va" string="Update" type="object" default_focus="1" class="oe_highlight"/> + <button string="Cancel" class="btn btn-secondary" special="cancel" /> + </footer> + </form> + </field> + </record> + + <record id="action_va_multi_reject" model="ir.actions.act_window"> + <field name="name">VA Multi Reject</field> + <field name="res_model">va.multi.reject</field> + <field name="type">ir.actions.act_window</field> + <field name="view_mode">form</field> + <field name="view_id" ref="view_va_multi_reject_form"/> + <field name="target">new</field> + </record> + </data> +</odoo>
\ No newline at end of file diff --git a/indoteknik_custom/views/partner_payment_term.xml b/indoteknik_custom/views/partner_payment_term.xml index 433cac3e..e0f2fe39 100644 --- a/indoteknik_custom/views/partner_payment_term.xml +++ b/indoteknik_custom/views/partner_payment_term.xml @@ -24,6 +24,9 @@ <field name="name"/> <field name="parent_id" readonly="1"/> <field name="property_payment_term_id"/> + <field name="active_limit"/> + <field name="warning_stage"/> + <field name="blocking_stage"/> </group> </group> </sheet> diff --git a/indoteknik_custom/views/product_product.xml b/indoteknik_custom/views/product_product.xml index c06cc5f1..71748e44 100644 --- a/indoteknik_custom/views/product_product.xml +++ b/indoteknik_custom/views/product_product.xml @@ -11,11 +11,14 @@ <field name="incoming_qty"/> </field> <field name="virtual_available" position="after"> + <field name="max_qty_reorder" optional="hide"/> <field name="qty_onhand_bandengan" optional="hide"/> <field name="qty_incoming_bandengan" optional="hide"/> <field name="qty_outgoing_bandengan" optional="hide"/> <field name="qty_available_bandengan" optional="hide"/> <field name="qty_free_bandengan" optional="hide"/> + <field name="qty_rpo" optional="hide"/> + <field name="plafon_qty" optional="hide"/> </field> </field> </record> diff --git a/indoteknik_custom/views/product_template.xml b/indoteknik_custom/views/product_template.xml index 5a509ebd..b6599137 100755 --- a/indoteknik_custom/views/product_template.xml +++ b/indoteknik_custom/views/product_template.xml @@ -24,6 +24,12 @@ <field name="public_categ_ids" position="attributes"> <attribute name="required">0</attribute> </field> + <div name="options" position="inside"> + <div> + <field name="merchandise_ok"/> + <label for="merchandise_ok"/> + </div> + </div> <field name="public_categ_ids" position="attributes"> <attribute name="options">{'no_create': True}</attribute> </field> diff --git a/indoteknik_custom/views/requisition.xml b/indoteknik_custom/views/requisition.xml index b704baaf..957113a7 100644 --- a/indoteknik_custom/views/requisition.xml +++ b/indoteknik_custom/views/requisition.xml @@ -50,6 +50,13 @@ <field name="model">requisition</field> <field name="arch" type="xml"> <form> + <header> + <button name="button_approve" + string="Approve" + type="object" + class="mr-2 oe_highlight" + /> + </header> <sheet string="Requisition"> <div class="oe_button_box" name="button_box"/> <group> @@ -62,12 +69,19 @@ </group> <group> <div> - <button name="create_po_from_requisition" - string="Create PO" - type="object" - class="mr-2 oe_highlight" + <button name="generate_requisition_from_so" + string="Create Line from SO" + type="object" + class="mr-2 oe_highlight" /> </div> + <div> + <button name="create_po_from_requisition" + string="Create PO" + type="object" + class="mr-2 oe_highlight" + /> + </div> </group> </group> <notebook> diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml index 6d02a86b..1b164161 100644 --- a/indoteknik_custom/views/res_partner.xml +++ b/indoteknik_custom/views/res_partner.xml @@ -6,6 +6,15 @@ <field name="model">res.partner</field> <field name="inherit_id" ref="base.view_partner_form"/> <field name="arch" type="xml"> + <field name="active_limit" position="attributes"> + <attribute name="readonly">1</attribute> + </field> + <field name="warning_stage" position="attributes"> + <attribute name="readonly">1</attribute> + </field> + <field name="blocking_stage" position="attributes"> + <attribute name="readonly">1</attribute> + </field> <field name="npwp" position="after"> <field name="sppkp"/> <field name="counter"/> @@ -22,7 +31,7 @@ <field name="industry_id" position="after"> <field name="company_type_id"/> <field name="group_partner_id"/> - <field name="is_potential"/> + <field name="is_not_potential"/> <field name="pareto_status"/> <field name="digital_invoice_tax"/> </field> diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 98001589..e12130de 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -82,7 +82,7 @@ <field name="email" required="1"/> <field name="unreserve_id"/> <field name="due_id" readonly="1"/> - <field name="vendor_approval_id" readonly="1"/> + <field name="vendor_approval_id" readonly="1" widget="many2many_tags"/> <field name="source_id" domain="[('id', 'in', [32, 59, 60, 61])]" required="1"/> <button name="override_allow_create_invoice" string="Override Create Invoice" @@ -118,8 +118,47 @@ } </attribute> </xpath> + <div name="invoice_lines" position="before"> + <div name="vendor_id" groups="base.group_no_one" attrs="{'invisible': [('display_type', '!=', False)]}"> + <label for="vendor_id"/> + <div name="vendor_id"> + <field name="vendor_id" + attrs="{'readonly': [('parent.approval_status', '=', 'approved')]}" + domain="[('parent_id', '=', False)]" + options="{'no_create': True}" class="oe_inline" /> + </div> + </div> + </div> + + <div name="invoice_lines" position="before"> + <div name="purchase_price" groups="base.group_no_one" attrs="{'invisible': [('display_type', '!=', False)]}"> + <label for="purchase_price"/> + <field name="purchase_price"/> + </div> + </div> + <div name="invoice_lines" position="before"> + <div name="purchase_tax_id" groups="base.group_no_one" attrs="{'invisible': [('display_type', '!=', False)]}"> + <label for="purchase_tax_id"/> + <div name="purchase_tax_id"> + <field name="purchase_tax_id"/> + </div> + </div> + </div> + <div name="invoice_lines" position="before"> + <div name="item_percent_margin" groups="base.group_no_one" attrs="{'invisible': [('display_type', '!=', False)]}"> + <label for="item_percent_margin"/> + <field name="item_percent_margin"/> + </div> + </div> + <div name="invoice_lines" position="before"> + <div name="price_subtotal" groups="base.group_no_one" attrs="{'invisible': [('display_type', '!=', False)]}"> + <label for="price_subtotal"/> + <field name="price_subtotal"/> + </div> + </div> <xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_total']" position="after"> - <field name="vendor_id" attrs="{'readonly': [('parent.approval_status', '=', 'approved')]}" domain="[('parent_id', '=', False)]" options="{'no_create':True}"/> + <field name="qty_free_bu" optional="hide"/> + <field name="vendor_id" attrs="{'readonly': [('parent.approval_status', '=', 'approved')], 'invisible': [('display_type', '!=', False)]}" domain="[('parent_id', '=', False)]" options="{'no_create':True}"/> <field name="vendor_md_id" optional="hide"/> <field name="purchase_price" attrs=" { @@ -227,6 +266,9 @@ <page string="Fullfillment" name="page_sale_order_fullfillment"> <field name="fullfillment_line" readonly="1"/> </page> + <page string="Fulfillment v2" name="page_sale_order_fullfillment2"> + <field name="fulfillment_line_v2" readonly="1"/> + </page> <page string="Reject Line" name="page_sale_order_reject_line"> <field name="reject_line" readonly="1"/> </page> @@ -342,6 +384,25 @@ </data> <data> + + </data> + <record id="sales_order_fulfillment_v2_tree" model="ir.ui.view"> + <field name="name">sales.order.fulfillment.v2.tree</field> + <field name="model">sales.order.fulfillment.v2</field> + <field name="arch" type="xml"> + <tree editable="top" create="false"> + <field name="product_id" readonly="1"/> + <field name="so_qty" readonly="1" optional="show"/> + <field name="reserved_stock_qty" readonly="1" optional="show"/> + <field name="delivered_qty" readonly="1" optional="hide"/> + <field name="po_ids" widget="many2many_tags" readonly="1" optional="show"/> + <field name="po_qty" readonly="1" optional="show"/> + <field name="received_qty" readonly="1" optional="show"/> + <field name="purchaser" readonly="1" optional="hide"/> + </tree> + </field> + </record> + <data> <record id="sales_order_fullfillmet_tree" model="ir.ui.view"> <field name="name">sales.order.fullfillment.tree</field> <field name="model">sales.order.fullfillment</field> diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index c230bc7b..1893fcaf 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -129,6 +129,7 @@ <page string="Delivery" name="delivery_order"> <group> <group> + <field name="notee"/> <field name="note_logistic"/> <field name="responsible" /> <field name="carrier_id"/> diff --git a/indoteknik_custom/views/vendor_approval.xml b/indoteknik_custom/views/vendor_approval.xml index 605edfbf..359c67d4 100644 --- a/indoteknik_custom/views/vendor_approval.xml +++ b/indoteknik_custom/views/vendor_approval.xml @@ -5,33 +5,24 @@ <field name="name">vendor.approval.tree</field> <field name="model">vendor.approval</field> <field name="arch" type="xml"> - <tree default_order="create_date desc" create="0"> + <tree default_order="create_date desc" create="0" editable="bottom"> <field name="number"/> <field name="create_date_so"/> <field name="order_id"/> <field name="partner_id"/> - <field name="state"/> - </tree> - </field> - </record> - - <record id="vendor_approval_line_tree" model="ir.ui.view"> - <field name="name">vendor.approval.line.tree</field> - <field name="model">vendor.approval.line</field> - <field name="arch" type="xml"> - <tree> + <field name="state" readonly="1" widget="badge" decoration-danger="state == 'cancel'" decoration-success="state == 'done'"/> <field name="product_id"/> - <field name="sales_price"/> - <field name="product_uom_qty"/> - <field name="sales_tax_id"/> - <field name="margin_after"/> + <field name="sales_price" optional="hide"/> + <field name="product_uom_qty" optional="hide"/> + <field name="sales_tax_id" optional="hide"/> + <field name="margin_after" optional="hide"/> <field name="vendor_id"/> <field name="vendor_md_id"/> - <field name="purchase_price"/> - <field name="purchase_price_md"/> - <field name="margin_before"/> - <field name="purchase_tax_id"/> - <field name="percent_margin_difference"/> + <field name="purchase_price" optional="hide"/> + <field name="purchase_price_md" optional="hide"/> + <field name="margin_before" optional="hide"/> + <field name="purchase_tax_id" optional="hide"/> + <field name="percent_margin_difference" optional="hide"/> </tree> </field> </record> @@ -60,13 +51,22 @@ <field name="order_id" readonly="1"/> <field name="state" readonly="1"/> <field name="create_date_so" readonly="1"/> + <field name="product_id"/> + <field name="sales_price"/> + <field name="product_uom_qty"/> + <field name="sales_tax_id"/> + </group> + <group> + <field name="margin_after"/> + <field name="vendor_id"/> + <field name="vendor_md_id"/> + <field name="purchase_price"/> + <field name="purchase_price_md"/> + <field name="margin_before"/> + <field name="purchase_tax_id"/> + <field name="percent_margin_difference"/> </group> </group> - <notebook> - <page string="SO Line"> - <field name="vendor_approval_line" readonly="1"/> - </page> - </notebook> </sheet> <div class="oe_chatter"> <field name="message_follower_ids" widget="mail_followers"/> @@ -77,7 +77,7 @@ </record> <record id="vendor_approval_view_search" model="ir.ui.view"> - <field name="name">vendor.approval.search.view</field> <!-- Made the name more descriptive --> + <field name="name">vendor.approval.search.view</field> <field name="model">vendor.approval</field> <field name="arch" type="xml"> <search string="Search Vendor Approval"> @@ -102,5 +102,19 @@ sequence="4" action="vendor_approval_action" /> + <record id="vendor_approval_multi_approve_ir_actions_server" model="ir.actions.server"> + <field name="name">Approve</field> + <field name="model_id" ref="model_vendor_approval"/> + <field name="binding_model_id" ref="model_vendor_approval"/> + <field name="state">code</field> + <field name="code">action = records.open_form_multi_approve()</field> + </record> + <record id="vendor_approval_multi_reject_ir_actions_server" model="ir.actions.server"> + <field name="name">Reject</field> + <field name="model_id" ref="model_vendor_approval"/> + <field name="binding_model_id" ref="model_vendor_approval"/> + <field name="state">code</field> + <field name="code">action = records.open_form_multi_reject()</field> + </record> </data> </odoo> |
