diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2024-11-13 16:13:18 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2024-11-13 16:13:18 +0700 |
| commit | ee8f00646d8a1d6faa1d91c82e9c3778cc8e41f8 (patch) | |
| tree | 926f1a022fd5ca18ac23f53b32abcd370f0bf5c1 | |
| parent | be2bc04768f9f423c66a612f4f183d20e70a3145 (diff) | |
| parent | e7a18de25926714222159cd9b9281b2fab62063b (diff) | |
Merge branch 'production' into iman/pengajuan-tempo
# Conflicts:
# indoteknik_custom/__manifest__.py
# indoteknik_custom/models/__init__.py
# indoteknik_custom/security/ir.model.access.csv
33 files changed, 569 insertions, 152 deletions
diff --git a/indoteknik_api/controllers/api_v1/cart.py b/indoteknik_api/controllers/api_v1/cart.py index 2a24b205..7a40b1e2 100644 --- a/indoteknik_api/controllers/api_v1/cart.py +++ b/indoteknik_api/controllers/api_v1/cart.py @@ -46,8 +46,25 @@ class Cart(controller.Controller): def get_cart_count_by_user_id(self, user_id, **kw): user_id = int(user_id) query = [('user_id', '=', user_id)] - carts = request.env['website.user.cart'].search_count(query) - return self.response(carts) + carts = request.env['website.user.cart'].search(query) + products_active = [] + products_inactive = [] + for cart in carts: + if cart.product_id: + price = cart.product_id._v2_get_website_price_include_tax() + if cart.product_id.active and price > 0: + product = cart.with_context(price_for="web").get_products() + for product_active in product: + products_active.append(product_active) + else: + product_inactives = cart.with_context(price_for="web").get_products() + for inactives in product_inactives: + products_inactive.append(inactives) + else: + program = cart.with_context(price_for="web").get_products() + for programs in program: + products_active.append(programs) + return self.response(len(products_active)) @http.route(PREFIX_USER + 'cart/create-or-update', auth='public', methods=['POST', 'OPTIONS'], csrf=False) @controller.Controller.must_authorized(private=True, private_key='user_id') diff --git a/indoteknik_api/controllers/api_v1/product.py b/indoteknik_api/controllers/api_v1/product.py index 9673b3ef..b68eb0f9 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.free_qty + + 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 e7664c79..3e182a2e 100644 --- a/indoteknik_api/controllers/api_v1/sale_order.py +++ b/indoteknik_api/controllers/api_v1/sale_order.py @@ -446,7 +446,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() @@ -463,6 +464,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 f0c7456d..ea8c6400 100644 --- a/indoteknik_api/controllers/api_v1/stock_picking.py +++ b/indoteknik_api/controllers/api_v1/stock_picking.py @@ -123,7 +123,7 @@ class StockPicking(controller.Controller): params = {'sj_documentation': sj_document, 'paket_documentation': paket_document, - 'driver_arrival_date': self.time_to_str(datetime.utcnow(), '%Y-%m-%d %H:%M:%S'), + 'driver_arrival_date': datetime.utcnow(), } picking_data = request.env['stock.picking'].search([('picking_code', '=', picking_code)], limit=1) diff --git a/indoteknik_api/models/sale_order.py b/indoteknik_api/models/sale_order.py index 725dbb4b..8e0371a3 100644 --- a/indoteknik_api/models/sale_order.py +++ b/indoteknik_api/models/sale_order.py @@ -84,6 +84,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 2ec5e720..3ec45699 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -146,6 +146,7 @@ 'views/approval_unreserve.xml', 'views/vendor_approval.xml', 'views/find_page.xml', + 'views/approval_retur_picking.xml', 'views/user_pengajuan_tempo.xml', 'report/report.xml', 'report/report_banner_banner.xml', diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index a2a445bc..e98d0d41 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -133,4 +133,5 @@ from . import vendor_approval from . import partner from . import find_page from . import user_pengajuan_tempo_line -from . import user_pengajuan_tempo
\ No newline at end of file +from . import user_pengajuan_tempo +from . import approval_retur_picking 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 07ddda1f..ba8b8da7 100644 --- a/indoteknik_custom/models/approval_unreserve.py +++ b/indoteknik_custom/models/approval_unreserve.py @@ -74,7 +74,7 @@ class ApprovalUnreserve(models.Model): raise UserError("Quantity yang di unreserve melebihi quantity yang ada") def action_approve(self): - if self.env.user.id != self.user_id.id: + if self.env.user.id != self.user_id.id and not self.env.user.has_group('indoteknik_custom.group_role_it'): raise UserError("Hanya Sales nya yang bisa approve.") if self.state != 'waiting_approval': diff --git a/indoteknik_custom/models/delivery_order.py b/indoteknik_custom/models/delivery_order.py index be5fd2e0..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.driver_arrival_date = current_time sale_order = False if picking.origin: @@ -103,14 +105,16 @@ class DeliveryOrderLine(models.TransientModel): delivery_type = self.get_delivery_type(picking.driver_departure_date, picking.driver_arrival_date) if delivery_type != 'departure': - self.departure_date = picking.driver_departure_date.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S') + if picking.driver_departure_date: + self.departure_date = picking.driver_departure_date.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S') if delivery_type == 'departure': self.departure_date = current_time elif delivery_type == 'arrival': self.arrival_date = current_time else: - self.arrival_date = picking.driver_arrival_date.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S') + if picking.driver_arrival_date: + self.arrival_date = picking.driver_arrival_date.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S') else: raise UserError('Nomor DO tidak ditemukan') diff --git a/indoteknik_custom/models/logbook_sj.py b/indoteknik_custom/models/logbook_sj.py index f84619ad..9f349882 100644 --- a/indoteknik_custom/models/logbook_sj.py +++ b/indoteknik_custom/models/logbook_sj.py @@ -101,14 +101,16 @@ class LogbookSJLine(models.TransientModel): delivery_type = self.get_delivery_type(picking.driver_departure_date, picking.driver_arrival_date) if delivery_type != 'departure': - self.departure_date = picking.driver_departure_date.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S') + if picking.driver_departure_date: + self.departure_date = picking.driver_departure_date.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S') if delivery_type == 'departure': self.departure_date = current_time elif delivery_type == 'arrival': self.arrival_date = current_time else: - self.arrival_date = picking.driver_arrival_date.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S') + if picking.driver_arrival_date: + self.arrival_date = picking.driver_arrival_date.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S') else: raise UserError('Nomor DO tidak ditemukan') diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 2ca4925b..25473ab8 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -374,6 +374,9 @@ 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') def _get_clean_website_description(self): for rec in self: @@ -389,23 +392,46 @@ class ProductProduct(models.Model): @api.constrains('active') def archive_product(self): for product in self: + if self.env.context.get('skip_unpublished_constraint'): + continue # Mencegah looping saat dipanggil dari metode lain + product_template = product.product_tmpl_id variants = product_template.product_variant_ids - if product_template.active and product.active: - if not product.active and len(variants) == 1: - product_template.with_context(skip_active_constraint=True).active = False - product_template.unpublished = True - elif not product.active and len(variants) > 1: + if len(variants) == 1: + # Jika hanya ada satu varian, atur status `unpublished` berdasarkan `active` + product_template.with_context(skip_unpublished_constraint=True).unpublished = not product.active + product.with_context(skip_unpublished_constraint=True).unpublished = not product.active + else: + if product.active: + product.with_context(skip_unpublished_constraint=True).unpublished = False + product_template.with_context(skip_unpublished_constraint=True).unpublished = any(variant.active for variant in variants) + else: + product.with_context(skip_unpublished_constraint=True).unpublished = True all_inactive = all(not variant.active for variant in variants) - if all_inactive: - product_template.with_context(skip_active_constraint=True).active = False - product_template.unpublished = True - else: - continue - if any(variant.active for variant in variants): - product_template.unpublished = False - variants.unpublished = False + product_template.with_context(skip_unpublished_constraint=True).unpublished = all_inactive + + @api.constrains('unpublished') + def archive_product_unpublished(self): + for product in self: + if self.env.context.get('skip_active_constraint'): + continue # Mencegah looping saat dipanggil dari metode lain + + product_template = product.product_tmpl_id + variants = product_template.product_variant_ids + + if len(variants) == 1: + # Jika hanya ada satu varian, atur status `unpublished` pada template, tetapi biarkan `active` tetap True + product_template.with_context(skip_active_constraint=True).unpublished = product.unpublished + else: + if not product.unpublished: + # Jika `unpublished` adalah False, pastikan `active` tetap True + product.with_context(skip_active_constraint=True).active = True + product_template.with_context(skip_active_constraint=True).active = any(not variant.unpublished for variant in variants) + else: + # Jika `unpublished` adalah True, atur template hanya jika semua varian di-unpublished + all_unpublished = all(variant.unpublished for variant in variants) + product_template.with_context(skip_active_constraint=True).active = not all_unpublished def update_internal_reference_variants(self, limit=100): variants = self.env['product.product'].search([ @@ -464,6 +490,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([ @@ -487,13 +523,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..be820c6f 100644 --- a/indoteknik_custom/models/promotion/sale_order.py +++ b/indoteknik_custom/models/promotion/sale_order.py @@ -6,6 +6,16 @@ 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: + self.env['sale.order.line'].create({ + 'order_id': self.id, + 'name': "Free Product " + free_product.product_id.display_name, + '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 393fc562..3397616d 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,6 +585,16 @@ 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 button_confirm(self): res = super(PurchaseOrder, self).button_confirm() current_time = datetime.now() @@ -595,7 +607,7 @@ class PurchaseOrder(models.Model): if self.total_percent_margin < self.total_so_percent_margin and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser') and not self.env.user.is_leader: raise UserError("Beda Margin dengan Sales, harus approval Merchandise") if not self.from_apo: - if not self.sale_order_id and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser') and not self.env.user.is_leader: + if not self.matches_so and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser') and not self.env.user.is_leader: raise UserError("Tidak ada link dengan SO, harus approval Merchandise") send_email = False @@ -635,7 +647,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,14 +736,46 @@ 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") + # 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() 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.sale_order_id: + elif self.total_percent_margin == self.total_so_percent_margin and self.matches_so and not greater_than_plafon: raise UserError("Bisa langsung Confirm") else: + reason = '' self.approval_status = 'pengajuan1' + if self.amount_untaxed >= 50000000: + reason = 'above 50jt, ' + if self.total_percent_margin < self.total_so_percent_margin: + reason += 'diff margin, ' + if not self.from_apo and not self.matches_so: + 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 + # 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>", + subtype_id=self.env.ref("mail.mt_note").id + ) + + 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: 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/sale_order.py b/indoteknik_custom/models/sale_order.py index 1ad08154..75332996 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.Many2one('vendor.approval', string="Vendor Approval", readonly=True, tracking=True, copy=False) customer_type = fields.Selection([ ('pkp', 'PKP'), ('nonpkp', 'Non PKP') @@ -148,7 +149,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 @@ -161,7 +162,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: @@ -186,7 +187,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: @@ -346,10 +347,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 @@ -490,8 +491,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 @@ -625,19 +626,19 @@ 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 + # 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') + # 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): @@ -768,7 +769,28 @@ class SaleOrder(models.Model): self._validate_order() for order in self: order.order_line.validate_line() + order.check_data_real_delivery_address() + order._validate_order() + order.sale_order_check_approve() + main_parent = order.partner_id.get_main_parent() + SYSTEM_UID = 25 + FROM_WEBSITE = order.create_uid.id == SYSTEM_UID + + if FROM_WEBSITE and main_parent.use_so_approval and order.web_approval not in ['cust_procurement','cust_director']: + raise UserError("This order not yet approved by customer procurement or director") + + if not order.client_order_ref and order.create_date > datetime(2024, 6, 27): + raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") + + if not order.commitment_date and order.create_date > datetime(2024, 9, 12): + raise UserError("Expected Delivery Date kosong, wajib diisi") + + if not order.real_shipping_id: + UserError('Real Delivery Address harus di isi') + + if order.validate_partner_invoice_due(): + return self._create_notification_action('Notification','Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension') term_days = 0 for term_line in order.payment_term_id.line_ids: diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 5e01067a..5a6640ec 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -31,6 +31,7 @@ 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') @@ -251,12 +252,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 +268,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: 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 7c10a910..dd1d40f6 100644 --- a/indoteknik_custom/models/solr/product_product.py +++ b/indoteknik_custom/models/solr/product_product.py @@ -67,6 +67,7 @@ class ProductProduct(models.Model): 'product_id_i': variant.id, 'template_id_i': variant.product_tmpl_id.id, 'image_s': ir_attachment.api_image('product.template', 'image_512', variant.product_tmpl_id.id), + 'image_mobile_s': ir_attachment.api_image('product.template', 'image_256', variant.product_tmpl_id.id), 'stock_total_f': variant.qty_stock_vendor, 'weight_f': variant.weight, 'manufacture_id_i': variant.product_tmpl_id.x_manufacture.id or 0, diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index 1d54cc9b..87e8370f 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -91,6 +91,7 @@ class ProductTemplate(models.Model): "product_rating_f": template.virtual_rating, "product_id_i": template.id, "image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id), + 'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id), "variant_total_i": template.product_variant_count, "stock_total_f": template.qty_stock_vendor, "weight_f": template.weight, diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 4c9d7658..50e9304b 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -104,6 +104,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'), @@ -192,9 +193,9 @@ 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): + # self.date_doc_kirim = self.driver_departure_date @api.constrains('arrival_time') def constrains_arrival_time(self): @@ -246,25 +247,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' @@ -444,9 +442,28 @@ class StockPicking(models.Model): if self.env.user.is_accounting: pick.approval_return_status = 'approved' else: - pick.approval_return_status = 'pengajuan1' + if self.picking_type_code == 'outgoing': + if self.env.user.id in [3988, 3401, 20]: + action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_stock_return_note_wizard') + action['context'] = { + 'picking_ids': [x.id for x in self] + } + return action + 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'): + action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_stock_return_note_wizard') + action['context'] = { + 'picking_ids': [x.id for x in self] + } + return action + 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: @@ -503,6 +520,10 @@ class StockPicking(models.Model): 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._name != 'stock.picking': return super(StockPicking, self).button_validate() @@ -553,6 +574,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): @@ -688,4 +717,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 86e66934..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 + 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/vendor_approval.py b/indoteknik_custom/models/vendor_approval.py index b0d58b85..bcc5d3ea 100644 --- a/indoteknik_custom/models/vendor_approval.py +++ b/indoteknik_custom/models/vendor_approval.py @@ -30,10 +30,10 @@ class VendorApproval(models.Model): 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) - + if not self.order_id.due_id: + self.order_id.action_confirm() def action_reject(self): if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): diff --git a/indoteknik_custom/models/wati.py b/indoteknik_custom/models/wati.py index d9fb7247..eed5413e 100644 --- a/indoteknik_custom/models/wati.py +++ b/indoteknik_custom/models/wati.py @@ -192,27 +192,6 @@ class WatiHistory(models.Model): is_get_attribute = fields.Boolean(string='Get Attribute', default=False) def _get_attribute_wati(self): - # url = 'https://live-server-2106.wati.io/api/v1/getContacts' - - # cookies = { - # 'affinity': '1701232090.884.1520.321410|ff187ffce9bc0bae13542bb446e41008', - # } - - # headers = { - # 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3MGM5ZmJhNy00MWRlLTRkMWEtYjY2NS1hM2Q5ODc2ZjhlZWIiLCJ1bmlxdWVfbmFtZSI6InR5YXNAaW5kb3Rla25pay5jb20iLCJuYW1laWQiOiJ0eWFzQGluZG90ZWtuaWsuY29tIiwiZW1haWwiOiJ0eWFzQGluZG90ZWtuaWsuY29tIiwiYXV0aF90aW1lIjoiMTEvMjkvMjAyMyAwNDoxNzo0NyIsImRiX25hbWUiOiIyMTA2IiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQURNSU5JU1RSQVRPUiIsImV4cCI6MjUzNDAyMzAwODAwLCJpc3MiOiJDbGFyZV9BSSIsImF1ZCI6IkNsYXJlX0FJIn0.--KHv4GCOG2MM3lNW9Nm-0-d8OAVpn5kbcSX4JKqATQ', - # # 'Cookie': 'affinity=1701232090.884.1520.321410|ff187ffce9bc0bae13542bb446e41008', - # } - - # files = { - # 'pageSize': (None, '1'), - # 'pageNumber': (None, '1'), - # 'name': (None, ''), - # 'attribute': (None, '[{name: "phone", operator: "contain", value: "6285751430014"}]'), - # 'createdDate': (None, ''), - # } - - # response = requests.get(url, cookies=cookies, headers=headers, files=files) - # print(response.json()) domain = [ '&', ('is_get_attribute', '=', False), @@ -226,29 +205,36 @@ class WatiHistory(models.Model): for wati_history in wati_histories: count += 1 _logger.info('[Parse Notification] Process: %s/%s' % (str(count), str(limit))) + wati_api = self.env['wati.api'] + + # Perbaikan pada params 'attribute' untuk menghindari masalah "type object is not subscriptable" params = { - 'pageSize':1, - 'pageNumber':1, - 'attribute':[{'name': "phone", 'operator': "contain", 'value': wati_history.wa_id}], + 'pageSize': 1, + 'pageNumber': 1, + 'attribute': json.dumps([{'name': "phone", 'operator': "contain", 'value': wati_history.wa_id}]), } + wati_contacts = wati_api.http_get('/api/v1/getContacts', params) - if wati_contacts['result'] != 'success': + + if wati_contacts.get('result') != 'success': return - json_dump = json.dumps(wati_contacts, indent=4, sort_keys=True) - contact_list = json.loads(json_dump)['contact_list'] + + contact_list = wati_contacts.get('contact_list', []) + perusahaan = email = '' for data in contact_list: - custom_params = data['customParams'] + custom_params = data.get('customParams', []) for custom_param in custom_params: - name = custom_param['name'] - value = custom_param['value'] + name = custom_param.get('name') + value = custom_param.get('value') if name == 'perusahaan': perusahaan = value elif name == 'email': email = value - # end for 2 - # end for 1 + # End inner loop + + # Update wati_history fields wati_history.perusahaan = perusahaan wati_history.email = email wati_history.is_get_attribute = True diff --git a/indoteknik_custom/models/website_user_cart.py b/indoteknik_custom/models/website_user_cart.py index 26e217cb..494f32f3 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'] = stock_quant.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 @@ -93,22 +95,31 @@ class WebsiteUserCart(models.Model): def get_product_by_user(self, user_id, selected=False, source=False): user_id = int(user_id) - + if source == 'buy': source = ['buy'] else: source = ['add_to_cart', 'buy'] parameters = [ - ('user_id', '=', user_id), + ('user_id', '=', user_id), ('source', 'in', source) ] + carts = self.search(parameters) + + for cart in carts: + if cart.product_id: + price = cart.product_id._v2_get_website_price_include_tax() + if not cart.product_id.active or price < 1: + cart.is_selected = False if selected: parameters.append(('is_selected', '=', True)) - carts = self.search(parameters) - products = carts.get_products() + products_active = self.search(parameters) + + products = products_active.get_products() + return products def get_user_checkout(self, user_id, voucher=False, voucher_shipping=False, source=False): diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 1369a03a..b582a587 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -144,5 +144,8 @@ access_vendor_approval_line,access.vendor.approval.line,model_vendor_approval_li 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_User_pengajuan_tempo_line,access.user.pengajuan.tempo.line,model_user_pengajuan_tempo_line,,1,1,1,1 access_user_pengajuan_tempo,access.user.pengajuan.tempo,model_user_pengajuan_tempo,,1,1,1,1
\ No newline at end of file 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/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/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml index 06c76a82..0e6b6792 100755 --- a/indoteknik_custom/views/purchase_order.xml +++ b/indoteknik_custom/views/purchase_order.xml @@ -212,7 +212,6 @@ <field name="code">model.procure_calculation()</field> <field name="state">code</field> <field name="priority">75</field> - <field name="active">True</field> </record> </data> <data> 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/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 98001589..b8f2d08d 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -227,6 +227,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 +345,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> |
