From eabbbadc5114f6c1edb9ec6bb74a296477f02b5a Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Thu, 14 Dec 2023 14:13:32 +0700 Subject: initial commit purchasing job --- indoteknik_custom/models/__init__.py | 2 ++ indoteknik_custom/models/purchasing_job.py | 42 ++++++++++++++++++++++ .../models/purchasing_job_multi_update.py | 22 ++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 indoteknik_custom/models/purchasing_job.py create mode 100644 indoteknik_custom/models/purchasing_job_multi_update.py (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 35f06f03..7a6b4251 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -93,3 +93,5 @@ from . import product_monitoring from . import account_bank_statement from . import stock_warehouse_orderpoint from . import commision +from . import purchasing_job +from . import purchasing_job_multi_update diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py new file mode 100644 index 00000000..c660b937 --- /dev/null +++ b/indoteknik_custom/models/purchasing_job.py @@ -0,0 +1,42 @@ +from odoo import fields, models, api, tools +import logging + +_logger = logging.getLogger(__name__) + + +class PurchasingJob(models.Model): + _name = 'v.purchasing.job' + _auto = False + _rec_name = 'product_id' + + id = fields.Integer() + product_id = fields.Many2one('product.product', string="Product") + brand = fields.Char(string='Brand') + item_code = fields.Char(string='Item Code') + product = fields.Char(string='Product Name') + onhand = fields.Float(string='OnHand') + incoming = fields.Float(string="Incoming") + outgoing = fields.Float(string="Outgoing") + action = fields.Char(string="Status") + + def init(self): + tools.drop_view_if_exists(self.env.cr, self._table) + self.env.cr.execute(""" + CREATE OR REPLACE VIEW %s AS ( + select product_id as id, product_id, brand, item_code, product, onhand, incoming, outgoing, action + from v_procurement_monitoring_by_product + where action = 'kurang beli' + ) + """ % self._table) + + def open_form_multi_generate_request_po(self): + action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_generate_request_po') + action['context'] = { + 'product_ids': [x.id for x in self] + } + return action + + def generate_request_po(self): + # print(1) + for product in self: + print(product) diff --git a/indoteknik_custom/models/purchasing_job_multi_update.py b/indoteknik_custom/models/purchasing_job_multi_update.py new file mode 100644 index 00000000..e455c5a4 --- /dev/null +++ b/indoteknik_custom/models/purchasing_job_multi_update.py @@ -0,0 +1,22 @@ +from odoo import models, fields +import logging + +_logger = logging.getLogger(__name__) + + +class PurchasingJobMultiUpdate(models.TransientModel): + _name = 'purchasing.job.multi.update' + + def save_multi_update_purchasing_job(self): + product_ids = self._context['product_ids'] + product = self.env['product.product'].browse(product_ids) + product.action_multi_update_invoice_status() + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Notification', + 'message': 'Invoice Status berhasil diubah', + 'next': {'type': 'ir.actions.act_window_close'}, + } + } \ No newline at end of file -- cgit v1.2.3 From ce2b8502ab85419096dda2bffaffbec4a096a99f Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Thu, 14 Dec 2023 15:16:55 +0700 Subject: bug fix window multi update --- indoteknik_custom/models/purchasing_job.py | 6 +++--- indoteknik_custom/models/purchasing_job_multi_update.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index c660b937..18235d47 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -30,7 +30,7 @@ class PurchasingJob(models.Model): """ % self._table) def open_form_multi_generate_request_po(self): - action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_generate_request_po') + action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_purchasing_job_multi_update') action['context'] = { 'product_ids': [x.id for x in self] } @@ -38,5 +38,5 @@ class PurchasingJob(models.Model): def generate_request_po(self): # print(1) - for product in self: - print(product) + for job in self: + print(job.product_id.name) diff --git a/indoteknik_custom/models/purchasing_job_multi_update.py b/indoteknik_custom/models/purchasing_job_multi_update.py index e455c5a4..c880980a 100644 --- a/indoteknik_custom/models/purchasing_job_multi_update.py +++ b/indoteknik_custom/models/purchasing_job_multi_update.py @@ -9,8 +9,8 @@ class PurchasingJobMultiUpdate(models.TransientModel): def save_multi_update_purchasing_job(self): product_ids = self._context['product_ids'] - product = self.env['product.product'].browse(product_ids) - product.action_multi_update_invoice_status() + product = self.env['v.purchasing.job'].browse(product_ids) + product.generate_request_po() return { 'type': 'ir.actions.client', 'tag': 'display_notification', -- cgit v1.2.3 From 1a68a5e600e2f7f90bee44144557f6af2f35618c Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Thu, 14 Dec 2023 15:42:41 +0700 Subject: add some field in automatic purchase --- indoteknik_custom/models/automatic_purchase.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index 502761e0..63eefb93 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -9,7 +9,10 @@ _logger = logging.getLogger(__name__) class AutomaticPurchase(models.Model): _name = 'automatic.purchase' _order = 'id desc' + _inherit = ['mail.thread'] + _rec_name = 'number' + number = fields.Char(string='Document No', index=True, copy=False, readonly=True) date_doc = fields.Date(string='Date', required=True, help='Isi tanggal hari ini') description = fields.Char(string='Description', help='bebas isi nya apa') purchase_lines = fields.One2many('automatic.purchase.line', 'automatic_purchase_id', string='Lines', auto_join=True) @@ -18,6 +21,16 @@ class AutomaticPurchase(models.Model): purchase_match = fields.One2many('automatic.purchase.match', 'automatic_purchase_id', string='Matches', auto_join=True) vendor_id = fields.Many2one('res.partner', string='Vendor', help='boleh kosong, jika diisi, maka hanya keluar data untuk vendor tersebut') responsible_id = fields.Many2one('res.users', string='Responsible', required=True) + apo_type = fields.Selection([ + ('regular', 'Regular Fulfill SO'), + ('reordering', 'Reordering Rule'), + ], string='Type') + + @api.model + def create(self, vals): + vals['number'] = self.env['ir.sequence'].next_by_code('automatic.purchase') or '0' + result = super(AutomaticPurchase, self).create(vals) + return result def create_po_from_automatic_purchase(self): if not self.purchase_lines: @@ -84,6 +97,7 @@ class AutomaticPurchase(models.Model): self.is_po = True def generate_automatic_purchase(self): + # for reordering rule only if self.purchase_lines: raise UserError('Sudah digenerate sebelumnya, hapus line terlebih dahulu') -- cgit v1.2.3 From 07e8a51fc023025d2cbde83ab0333e5e53e2ad12 Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Thu, 14 Dec 2023 17:13:46 +0700 Subject: generate automatic purchase line from purchasing job --- indoteknik_custom/models/automatic_purchase.py | 71 ++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index 63eefb93..438d365f 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -18,13 +18,14 @@ class AutomaticPurchase(models.Model): purchase_lines = fields.One2many('automatic.purchase.line', 'automatic_purchase_id', string='Lines', auto_join=True) notification = fields.Char(string='Notification') is_po = fields.Boolean(string='Is PO') - purchase_match = fields.One2many('automatic.purchase.match', 'automatic_purchase_id', string='Matches', auto_join=True) + purchase_match = fields.One2many('automatic.purchase.match', 'automatic_purchase_id', string='PO Matches', auto_join=True) vendor_id = fields.Many2one('res.partner', string='Vendor', help='boleh kosong, jika diisi, maka hanya keluar data untuk vendor tersebut') responsible_id = fields.Many2one('res.users', string='Responsible', required=True) apo_type = fields.Selection([ ('regular', 'Regular Fulfill SO'), ('reordering', 'Reordering Rule'), - ], string='Type') + ], string='Type', tracking=3) + sales_match = fields.One2many('automatic.purchase.sales.match', 'automatic_purchase_id', string='SO Matches', auto_join=True) @api.model def create(self, vals): @@ -95,9 +96,56 @@ class AutomaticPurchase(models.Model): _logger.info('Automatic Create PO Line %s' % product.product_id.name) self.notification = self.notification + ' %s' % new_po.name self.is_po = True - + + def generate_regular_purchase(self): + if self.apo_type == 'reordering': + raise UserError('Tombol ini hanya untuk Regular Fulfill SO') + if self.vendor_id: + raise UserError('Vendor tidak dapat diisi jika Regular Fulfill SO') + if self.purchase_lines: + raise UserError('Sudah digenerate sebelumnya, hapus line terlebih dahulu') + #TODO must add order by for fifo mechanism + #change the view of v.purchasing.job and use order by in query + query = [ + ('action', '=', 'kurang beli') + ] + jobs = self.env['v.purchasing.job'].search(query) + count = 0 + for job in jobs: + qty_purchase = job.outgoing - job.onhand + job.incoming + qty_available = job.onhand + job.incoming - job.outgoing + + domain = [ + ('product_id.id', '=', job.product_id.id), + ] + orderby = 'count_trx_po desc, count_trx_po_vendor desc' + purchase_pricelist = self.env['purchase.pricelist'].search(domain, order=orderby, limit=1) + + vendor_id = purchase_pricelist.vendor_id + price = self._get_valid_purchase_price(purchase_pricelist) + last_po_line = self.env['purchase.order.line'].search([('product_id', '=', job.product_id.id), ('order_id.state', '=', 'done')], order='id desc', limit=1) + + self.env['automatic.purchase.line'].create([{ + 'automatic_purchase_id': self.id, + 'product_id': job.product_id.id, + 'qty_purchase': qty_purchase, + 'qty_available': qty_available, + 'partner_id': vendor_id.id, + 'last_price': price, + 'subtotal': qty_purchase * price, + 'last_order_id': last_po_line.order_id.id, + 'last_orderline_id': last_po_line.id, + 'brand_id': job.product_id.product_tmpl_id.x_manufacture.id + }]) + count += 1 + _logger.info('Create Automatic Purchase Line %s' % job.product_id.name) + self.notification = "Automatic PO Created %s Lines" % count + print(1) + def generate_automatic_purchase(self): # for reordering rule only + if self.apo_type == 'regular': + raise UserError('Tombol ini hanya untuk Reordering Rule') if self.purchase_lines: raise UserError('Sudah digenerate sebelumnya, hapus line terlebih dahulu') @@ -187,6 +235,11 @@ class AutomaticPurchaseLine(models.Model): current_po_line_id = fields.Many2one('purchase.order.line', string='Current Line') brand_id = fields.Many2one('x_manufactures', string='Brand') + @api.onchange('last_price', 'qty_purchase') + def _calculate_subtotal(self): + for line in self: + line.subtotal = line.qty_purchase * line.last_price + class AutomaticPurchaseMatch(models.Model): _name = 'automatic.purchase.match' @@ -201,3 +254,15 @@ class AutomaticPurchaseMatch(models.Model): for match in self: match.vendor = match.order_id.partner_id.name match.total = match.order_id.amount_total + + +class AutomaticPurchaseSalesMatch(models.Model): + _name = 'automatic.purchase.sales.match' + _order = 'automatic_purchase_id, id' + + automatic_purchase_id = fields.Many2one('automatic.purchase', string='Ref', required=True, ondelete='cascade', index=True, copy=False) + sale_id = fields.Many2one('sale.order', string='SO') + partner_id = fields.Many2one('res.partner', string='Customer') + salesperson_id = fields.Many2one('res.user', string='Sales') + product_id = fields.Many2one('product.product', string='Product') + qty_so = fields.Float(string='Qty') -- cgit v1.2.3 From e2086bc4f96391260fb3430537feddbc25bdde40 Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Tue, 19 Dec 2023 09:32:09 +0700 Subject: over to nathan --- indoteknik_custom/models/automatic_purchase.py | 51 ++++++++++++++++++++-- indoteknik_custom/models/purchasing_job.py | 47 ++++++++++++++++++++ .../models/purchasing_job_multi_update.py | 2 +- 3 files changed, 96 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index 438d365f..a7b24b4f 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -26,6 +26,18 @@ class AutomaticPurchase(models.Model): ('reordering', 'Reordering Rule'), ], string='Type', tracking=3) sales_match = fields.One2many('automatic.purchase.sales.match', 'automatic_purchase_id', string='SO Matches', auto_join=True) + total_qty_line = fields.Float(string='Total Qty Line', compute='_compute_total_qty') + total_qty_so = fields.Float(string='Total Qty SO', compute='_compute_total_qty') + + def _compute_total_qty(self): + for data in self: + qty_line = qty_so = 0 + for line in self.purchase_lines: + qty_line = qty_line + line.qty_purchase + for line in self.sales_match: + qty_so = qty_so + line.qty_so + data.total_qty_line = qty_line + data.total_qty_so = qty_so @api.model def create(self, vals): @@ -140,6 +152,33 @@ class AutomaticPurchase(models.Model): count += 1 _logger.info('Create Automatic Purchase Line %s' % job.product_id.name) self.notification = "Automatic PO Created %s Lines" % count + self._create_sales_matching() + print(1) + + def _create_sales_matching(self): + for line in self.purchase_lines: + domain = [ + ('product_id', '=', line.product_id.id) + ] + sales = self.env['v.sales.outstanding'].search(domain) + for sale in sales: + if line.qty_purchase > sale.outgoing: + qty_po = sale.outgoing + else: + qty_po = line.qty_purchase + 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': qty_po, + }]) print(1) def generate_automatic_purchase(self): @@ -261,8 +300,14 @@ class AutomaticPurchaseSalesMatch(models.Model): _order = 'automatic_purchase_id, id' automatic_purchase_id = fields.Many2one('automatic.purchase', string='Ref', required=True, ondelete='cascade', index=True, copy=False) + automatic_purchase_line_id = fields.Many2one('automatic.purchase.line', string='Automatic Line') sale_id = fields.Many2one('sale.order', string='SO') - partner_id = fields.Many2one('res.partner', string='Customer') - salesperson_id = fields.Many2one('res.user', string='Sales') + sale_line_id = fields.Many2one('sale.order.line', string='SO Line') + picking_id = fields.Many2one('stock.picking', string='Picking') + move_id = fields.Many2one('stock.move', string='Move') + partner_id = fields.Many2one('res.partner', string='Partner') + partner_invoice_id = fields.Many2one('res.partner', string='Invoice Partner') + salesperson_id = fields.Many2one('res.users', string='Sales') product_id = fields.Many2one('product.product', string='Product') - qty_so = fields.Float(string='Qty') + qty_so = fields.Float(string='Qty SO') + qty_po = fields.Float(string='Qty PO') diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index 18235d47..43da2458 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -38,5 +38,52 @@ class PurchasingJob(models.Model): def generate_request_po(self): # print(1) + # TODO create document automatic purchase for job in self: print(job.product_id.name) + + +class OutstandingSales(models.Model): + _name = 'v.sales.outstanding' + _auto = False + _rec_name = 'move_id' + + id = fields.Integer() + move_id = fields.Many2one('stock.move', string='Move') + picking_id = fields.Many2one('stock.picking', string='Picking') + product_id = fields.Many2one('product.product', string='Product') + sale_id = fields.Many2one('sale.order', string='Sale') + sale_line_id = fields.Many2one('sale.order.line', string='Sale Line') + partner_id = fields.Many2one('res.partner', string='Partner') + partner_invoice_id = fields.Many2one('res.partner', string='Invoice Partner') + salesperson_id = fields.Many2one('res.users', string='Salesperson') + origin = fields.Char(string='Origin') + salesperson = fields.Char(string='Sales Name') + item_code = fields.Char(string='Item Code') + product = fields.Char(string='Product') + outgoing = fields.Float(string='Outgoing') + brand = fields.Char(string='Brand') + invoice_partner = fields.Char(string='Invoice Partner') + + def init(self): + tools.drop_view_if_exists(self.env.cr, self._table) + self.env.cr.execute(""" + CREATE OR REPLACE VIEW v_sales_outstanding AS ( + select sm.id, sm.id as move_id, sp.id as picking_id, sm.product_id, so.id as sale_id, + sol.id as sale_line_id, rp.id as partner_id, so.user_id as salesperson_id, so.partner_invoice_id, + sp.origin, rp2.name as salesperson, coalesce(pp.default_code, pt.default_code) as item_code, pt.name as product, + sm.product_uom_qty as outgoing, xm.x_name as brand, rp.name as invoice_partner + from stock_move sm + join stock_picking sp on sp.id = sm.picking_id + join sale_order_line sol on sol.id = sm.sale_line_id + join sale_order so on so.id = sol.order_id + join res_partner rp on rp.id = so.partner_invoice_id + join res_users ru on ru.id = so.user_id + join res_partner rp2 on rp2.id = ru.partner_id + join product_product pp on pp.id = sm.product_id + join product_template pt on pt.id = pp.product_tmpl_id + left join x_manufactures xm on xm.id = pt.x_manufacture + where sp.state in ('draft', 'waiting', 'confirmed', 'assigned') + and sp.name like '%OUT%' + ) + """) diff --git a/indoteknik_custom/models/purchasing_job_multi_update.py b/indoteknik_custom/models/purchasing_job_multi_update.py index c880980a..a1b79907 100644 --- a/indoteknik_custom/models/purchasing_job_multi_update.py +++ b/indoteknik_custom/models/purchasing_job_multi_update.py @@ -16,7 +16,7 @@ class PurchasingJobMultiUpdate(models.TransientModel): 'tag': 'display_notification', 'params': { 'title': 'Notification', - 'message': 'Invoice Status berhasil diubah', + 'message': 'Berhasil membuat Automatic Purchase', 'next': {'type': 'ir.actions.act_window_close'}, } } \ No newline at end of file -- cgit v1.2.3 From 9abad8c8ee173626b05d13a5db9e81352dce0bb6 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 22 Dec 2023 09:04:34 +0700 Subject: Add field on model - sale_automatic_id (purchase.order.line) - order_sales_match_line (sale.order) --- indoteknik_custom/models/purchase_order_line.py | 1 + indoteknik_custom/models/sale_order.py | 2 ++ 2 files changed, 3 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order_line.py b/indoteknik_custom/models/purchase_order_line.py index f1c20106..06ffd57a 100755 --- a/indoteknik_custom/models/purchase_order_line.py +++ b/indoteknik_custom/models/purchase_order_line.py @@ -33,6 +33,7 @@ class PurchaseOrderLine(models.Model): indent = fields.Boolean(string='Indent', help='centang ini jika barang indent') is_ltc = fields.Boolean(string='Sudah di LTC', default=False, help='centang ini jika barang sudah di LTC') note = fields.Char(string='Note') + sale_automatic_id = fields.Many2one('sale.order', string='SO') def suggest_purchasing(self): for line in self: diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 9f31c0fd..d1a3015d 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -8,6 +8,8 @@ _logger = logging.getLogger(__name__) class SaleOrder(models.Model): _inherit = "sale.order" + + order_sales_match_line = fields.One2many('sales.order.purchase.match', 'sales_order_id', string='Purchase Match Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True) total_margin = fields.Float('Total Margin', compute='_compute_total_margin', help="Total Margin in Sales Order Header") total_percent_margin = fields.Float('Total Percent Margin', compute='_compute_total_percent_margin', help="Total % Margin in Sales Order Header") approval_status = fields.Selection([ -- cgit v1.2.3 From 8a10587c8582ed68944634928c9a7c34d3321dbe Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 22 Dec 2023 13:35:26 +0700 Subject: matches so on po and matches po on so --- indoteknik_custom/models/__init__.py | 2 + indoteknik_custom/models/automatic_purchase.py | 237 ++++++++++++++++----- indoteknik_custom/models/product_template.py | 5 + indoteknik_custom/models/purchase_order.py | 1 + indoteknik_custom/models/purchase_order_line.py | 10 +- .../models/purchase_order_sales_match.py | 22 ++ .../models/sales_order_purchase_match.py | 22 ++ 7 files changed, 244 insertions(+), 55 deletions(-) create mode 100644 indoteknik_custom/models/purchase_order_sales_match.py create mode 100644 indoteknik_custom/models/sales_order_purchase_match.py (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 4faf969d..fb986c0d 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -96,3 +96,5 @@ from . import commision from . import sale_advance_payment_inv from . import purchasing_job from . import purchasing_job_multi_update +from . import purchase_order_sales_match +from . import sales_order_purchase_match diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index a7b24b4f..070db78f 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -1,7 +1,7 @@ from odoo import models, api, fields from odoo.exceptions import UserError from datetime import datetime -import logging +import logging, math _logger = logging.getLogger(__name__) @@ -50,64 +50,195 @@ class AutomaticPurchase(models.Model): raise UserError('Tidak ada Lines, belum bisa create PO') if self.is_po: raise UserError('Sudah pernah di create PO') + current_time = datetime.now() - vendor_ids = self.env['automatic.purchase.line'].read_group([('automatic_purchase_id', '=', self.id), ('partner_id', '!=', False)], fields=['partner_id'], groupby=['partner_id']) - - counter_po_number = 0 + vendor_ids = self.env['automatic.purchase.line'].read_group( + [('automatic_purchase_id', '=', self.id), ('partner_id', '!=', False)], + fields=['partner_id'], + groupby=['partner_id'] + ) + + counter = 0 for vendor in vendor_ids: - param_header = { - 'partner_id': vendor['partner_id'][0], - # 'partner_ref': 'Automatic PO', - 'currency_id': 12, - 'user_id': self.env.user.id, - 'company_id': 1, # indoteknik dotcom gemilang - 'picking_type_id': 28, # indoteknik bandengan receipts - 'date_order': current_time, - 'note_description': 'Automatic PO' - } + self.create_po_by_vendor(vendor['partner_id'][0]) + + # param_header = { + # 'partner_id': vendor['partner_id'][0], + # 'currency_id': 12, + # 'user_id': self.env.user.id, + # 'company_id': 1, # indoteknik dotcom gemilang + # 'picking_type_id': 28, # indoteknik bandengan receipts + # 'date_order': current_time, + # 'note_description': 'Automatic PO' + # } + + # products_vendors = self.env['automatic.purchase.line'].search([ + # ('automatic_purchase_id', '=', self.id), + # ('partner_id', '=', vendor['partner_id'][0]), + # ('qty_purchase', '>', 0) + # ], order='brand_id') + # counter += 1 # new_po = self.env['purchase.order'].create([param_header]) - products_vendors = self.env['automatic.purchase.line'].search([ - ('automatic_purchase_id', '=', self.id), - ('partner_id', '=', vendor['partner_id'][0]), - ('qty_purchase', '>', 0) - ], order='brand_id') - count = brand_id = 0 - for product in products_vendors: - if count == 200 or brand_id != product.brand_id.id: - count = 0 - counter_po_number += 1 - new_po = self.env['purchase.order'].create([param_header]) - new_po.name = new_po.name + "/A/"+str(counter_po_number) - self.env['automatic.purchase.match'].create([{ - 'automatic_purchase_id': self.id, - 'order_id': new_po.id - }]) - self.env.cr.commit() - # else: - # new_po = self.env['purchase.order'].create([param_header]) - brand_id = product.brand_id.id - count += 10 - - qty_available = product.product_id.qty_onhand_bandengan + product.product_id.qty_incoming_bandengan - product.product_id.outgoing_qty - suggest = 'harus beli' - if qty_available > product.qty_purchase: - suggest = 'masih cukup' + # new_po.name = new_po.name + "/A/" + str(counter) + # self.env['automatic.purchase.match'].create([{ + # 'automatic_purchase_id': self.id, + # 'order_id': new_po.id + # }]) + # self.env.cr.commit() + + # count = 0 + # for product in products_vendors: + # if count == 20: + # self.create_purchase_order_sales_match(new_po) + + # if count == 20: + # counter += 1 + # new_po = self.env['purchase.order'].create([param_header]) + # new_po.name = new_po.name + "/B/" + str(counter) + # self.env['automatic.purchase.match'].create([{ + # 'automatic_purchase_id': self.id, + # 'order_id': new_po.id + # }]) + + # self.create_purchase_order_sales_match(new_po) + # self.env.cr.commit() + + # count += 1 + # qty_available = ( + # product.product_id.qty_onhand_bandengan + + # product.product_id.qty_incoming_bandengan - + # product.product_id.outgoing_qty + # ) + # suggest = 'harus beli' if qty_available <= product.qty_purchase else 'masih cukup' + # param_line = { + # 'order_id': new_po.id, + # 'product_id': product.product_id.id, + # 'product_qty': product.qty_purchase, + # 'qty_available_store': qty_available, + # 'suggest': suggest, + # 'product_uom_qty': product.qty_purchase, + # 'price_unit': product.last_price, + # } + # new_line = self.env['purchase.order.line'].create([param_line]) + # product.current_po_id = new_po.id + # product.current_po_line_id = new_line.id + + # self.create_purchase_order_sales_match(new_po) + + self.notification = 'PO Created successfully' + self.is_po = True + + def create_po_by_vendor(self, vendor_id): + current_time = datetime.now() + + PRODUCT_PER_PO = 20 + + auto_purchase_line = self.env['automatic.purchase.line'] + + param_header = { + 'partner_id': vendor_id, + 'currency_id': 12, + 'user_id': self.env.user.id, + 'company_id': 1, # indoteknik dotcom gemilang + 'picking_type_id': 28, # indoteknik bandengan receipts + 'date_order': current_time, + 'note_description': 'Automatic PO' + } + + domain = [ + ('automatic_purchase_id', '=', self.id), + ('partner_id', '=', vendor_id), + ('qty_purchase', '>', 0) + ] + + products_len = auto_purchase_line.search_count(domain) + page = math.ceil(products_len / PRODUCT_PER_PO) + + # i start from zero (0) + for i in range(page): + new_po = self.env['purchase.order'].create([param_header]) + new_po.name = new_po.name + "/A/" + str(i + 1) + + self.env['automatic.purchase.match'].create([{ + 'automatic_purchase_id': self.id, + 'order_id': new_po.id + }]) + + lines = auto_purchase_line.search( + domain, + offset=i * PRODUCT_PER_PO, + limit=PRODUCT_PER_PO + ) + + for line in lines: + product = line.product_id param_line = { 'order_id': new_po.id, - 'sequence': count, - 'product_id': product.product_id.id, - 'product_qty': product.qty_purchase, - 'qty_available_store': qty_available, - 'suggest': suggest, - 'product_uom_qty': product.qty_purchase, - 'price_unit': product.last_price, + 'product_id': product.id, + 'product_qty': line.qty_purchase, + 'qty_available_store': product.qty_available_bandengan, + 'suggest': product._get_po_suggest(line.qty_purchase), + 'product_uom_qty': line.qty_purchase, + 'price_unit': line.last_price, } - new_line = self.env['purchase.order.line'].create([param_line]) - product.current_po_id = new_po.id - product.current_po_line_id = new_line.id - _logger.info('Automatic Create PO Line %s' % product.product_id.name) - self.notification = self.notification + ' %s' % new_po.name - self.is_po = True + new_po_line = self.env['purchase.order.line'].create([param_line]) + line.current_po_id = new_po.id + line.current_po_line_id = new_po_line.id + + self.create_purchase_order_sales_match(new_po) + + + def create_purchase_order_sales_match(self, purchase_order): + matches_so_product_ids = [line.product_id.id for line in purchase_order.order_line] + + matches_so = self.env['automatic.purchase.sales.match'].search([ + ('automatic_purchase_id', '=', self.id), + ('sale_line_id.product_id', 'in', matches_so_product_ids), + ]) + + for sale_order in matches_so: + matches_so_line = { + 'purchase_order_id': purchase_order.id, + 'sale_id': sale_order.sale_id.id, + 'sale_line_id': sale_order.sale_line_id.id, + 'picking_id': sale_order.picking_id.id, + 'move_id': sale_order.move_id.id, + 'partner_id': sale_order.partner_id.id, + 'partner_invoice_id': sale_order.partner_invoice_id.id, + 'salesperson_id': sale_order.salesperson_id.id, + 'product_id': sale_order.product_id.id, + 'qty_so': sale_order.qty_so, + 'qty_po': sale_order.qty_po, + } + po_matches_so_line = self.env['purchase.order.sales.match'].create([matches_so_line]) + + self.create_sales_order_purchase_match(purchase_order) + + def create_sales_order_purchase_match(self, purchase_order): + #TODO add matches po to sales order by automatic_purchase.sales.match + matches_po_product_ids = [line.product_id.id for line in purchase_order.order_line] + + sales_match_line = self.env['automatic.purchase.sales.match'].search([ + ('automatic_purchase_id', '=', self.id), + ('sale_line_id.product_id', 'in', matches_po_product_ids), + ]) + + for sales_match in sales_match_line: + purchase_line = self.env['automatic.purchase.line'].search([ + ('automatic_purchase_id', '=', self.id), + ('product_id', 'in', [sales_match.product_id.id]), + ]) + + matches_po_line = { + 'sales_order_id' : sales_match.sale_id.id, + 'purchase_order_id' : purchase_line.current_po_id.id, + 'purchase_line_id' : purchase_line.current_po_line_id.id, + 'product_id' : sales_match.product_id.id, + 'qty_so' : sales_match.qty_so, + 'qty_po' : sales_match.qty_po, + } + + sales_order_purchase_match = self.env['sales.order.purchase.match'].create([matches_po_line]) def generate_regular_purchase(self): if self.apo_type == 'reordering': diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 34aff4fa..7ec5a002 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -347,6 +347,11 @@ class ProductProduct(models.Model): sla_version = fields.Integer(string="SLA Version", default=0) is_edited = fields.Boolean(string='Is Edited') qty_sold = fields.Float(string='Sold Quantity', compute='_get_qty_sold') + + def _get_po_suggest(self, qty_purchase): + if self.qty_available_bandengan < qty_purchase: + return 'harus beli' + return 'masih cukup' def _get_qty_upcoming(self): for product in self: diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 285c5a95..82ca3108 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -16,6 +16,7 @@ _logger = logging.getLogger(__name__) class PurchaseOrder(models.Model): _inherit = 'purchase.order' + order_sales_match_line = fields.One2many('purchase.order.sales.match', 'purchase_order_id', string='Sales Match Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True) sale_order_id = fields.Many2one('sale.order', string='Sale Order') procurement_status = fields.Char(string='Procurement Status', compute='get_procurement_status', readonly=True) po_status = fields.Selection([ diff --git a/indoteknik_custom/models/purchase_order_line.py b/indoteknik_custom/models/purchase_order_line.py index 95813e0d..8d7d818b 100755 --- a/indoteknik_custom/models/purchase_order_line.py +++ b/indoteknik_custom/models/purchase_order_line.py @@ -34,10 +34,16 @@ class PurchaseOrderLine(models.Model): is_ltc = fields.Boolean(string='Sudah di LTC', default=False, help='centang ini jika barang sudah di LTC') note = fields.Char(string='Note') sale_automatic_id = fields.Many2one('sale.order', string='SO') + + + # so_line.qty_reserved (compute) + # so_line.qty_reserved = get from picking_ids where type outgoing and prodid = line.prodid + # po_line.qty_reserved = cek dulu apakah ada relasi ke sale order. Jika ada maka ambil sesuai yang ada di sale order (so_line.qty_reserved), + # jika tidak maka 0 def suggest_purchasing(self): - for line in self: - if line.qty_available < line.product_qty: + for line in self: + if line.product_id.qty_available_bandengan + line.qty_reserved < line.product_qty: line.suggest = 'harus beli' else: line.suggest = 'masih cukup' diff --git a/indoteknik_custom/models/purchase_order_sales_match.py b/indoteknik_custom/models/purchase_order_sales_match.py new file mode 100644 index 00000000..02c19b1f --- /dev/null +++ b/indoteknik_custom/models/purchase_order_sales_match.py @@ -0,0 +1,22 @@ +from odoo import fields, models, api, _ +from odoo.exceptions import AccessError, UserError, ValidationError +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT +import logging + +_logger = logging.getLogger(__name__) + + +class PurchaseOrderSalesMatch(models.Model): + _name = 'purchase.order.sales.match' + + purchase_order_id = fields.Many2one('purchase.order', string='Purchase Order', index=True, required=True, ondelete='cascade') + sale_id = fields.Many2one('sale.order', string='SO') + sale_line_id = fields.Many2one('sale.order.line', string='SO Line') + picking_id = fields.Many2one('stock.picking', string='Picking') + move_id = fields.Many2one('stock.move', string='Move') + partner_id = fields.Many2one('res.partner', string='Partner') + partner_invoice_id = fields.Many2one('res.partner', string='Invoice Partner') + salesperson_id = fields.Many2one('res.users', string='Sales') + product_id = fields.Many2one('product.product', string='Product') + qty_so = fields.Float(string='Qty SO') + qty_po = fields.Float(string='Qty PO') \ No newline at end of file diff --git a/indoteknik_custom/models/sales_order_purchase_match.py b/indoteknik_custom/models/sales_order_purchase_match.py new file mode 100644 index 00000000..f5436335 --- /dev/null +++ b/indoteknik_custom/models/sales_order_purchase_match.py @@ -0,0 +1,22 @@ +from odoo import fields, models, api, _ +from odoo.exceptions import AccessError, UserError, ValidationError +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT +import logging + +_logger = logging.getLogger(__name__) + + +class SalesOrderPurchaseMatch(models.Model): + _name = 'sales.order.purchase.match' + + sales_order_id = fields.Many2one('sale.order', string='Sale Order', index=True, required=True, ondelete='cascade') + purchase_order_id = fields.Many2one('purchase.order', string='SO') + purchase_line_id = fields.Many2one('purchase.order.line', string='SO Line') + product_id = fields.Many2one('product.product', string='Product') + qty_so = fields.Float(string='Qty SO') + qty_po = fields.Float(string='Qty PO') + # picking_id = fields.Many2one('stock.picking', string='Picking') + # move_id = fields.Many2one('stock.move', string='Move') + # partner_id = fields.Many2one('res.partner', string='Partner') + # partner_invoice_id = fields.Many2one('res.partner', string='Invoice Partner') + # salesperson_id = fields.Many2one('res.users', string='Sales') \ No newline at end of file -- cgit v1.2.3 From 624e4077925cf1517097da015076dd4385cf286c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 27 Dec 2023 10:13:47 +0700 Subject: validate if product have po --- indoteknik_custom/models/automatic_purchase.py | 21 +++++++++++++++++++++ indoteknik_custom/models/purchasing_job.py | 1 + .../models/sales_order_purchase_match.py | 4 ++-- 3 files changed, 24 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index 070db78f..5ab05aed 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -44,6 +44,25 @@ class AutomaticPurchase(models.Model): vals['number'] = self.env['ir.sequence'].next_by_code('automatic.purchase') or '0' result = super(AutomaticPurchase, self).create(vals) return result + + def validate_so_is_po(self): + #TODO user cant create po if in so exist matches po + for line in self.purchase_lines: + # Assuming that 'order_sales_match_line' is a field on the model 'sale.order' + sale_id = self.env['sale.order'].search([ + ('id', '=', line.sale_id.id), + ('order_sales_match_line', '!=', False) + ]) + + # Check if there are multiple records + if len(sale_id) > 1: + raise UserError('Ada SO Yang sudah create PO, berikut SO nya: %s' % [sale.name for sale in sale_id]) + + # Check if there is exactly one record + if sale_id: + # Access the first record in the set + sale_id = sale_id[0] + raise UserError('Ada SO Yang sudah create PO, berikut SO nya: %s' % [sale.name for sale in sale_id]) def create_po_from_automatic_purchase(self): if not self.purchase_lines: @@ -51,6 +70,8 @@ class AutomaticPurchase(models.Model): if self.is_po: raise UserError('Sudah pernah di create PO') + self.validate_so_is_po() + current_time = datetime.now() vendor_ids = self.env['automatic.purchase.line'].read_group( [('automatic_purchase_id', '=', self.id), ('partner_id', '!=', False)], diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index 43da2458..09c202d9 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -18,6 +18,7 @@ class PurchasingJob(models.Model): incoming = fields.Float(string="Incoming") outgoing = fields.Float(string="Outgoing") action = fields.Char(string="Status") + is_po = fields.Boolean(string='Is PO') def init(self): tools.drop_view_if_exists(self.env.cr, self._table) diff --git a/indoteknik_custom/models/sales_order_purchase_match.py b/indoteknik_custom/models/sales_order_purchase_match.py index f5436335..042d5855 100644 --- a/indoteknik_custom/models/sales_order_purchase_match.py +++ b/indoteknik_custom/models/sales_order_purchase_match.py @@ -10,8 +10,8 @@ class SalesOrderPurchaseMatch(models.Model): _name = 'sales.order.purchase.match' sales_order_id = fields.Many2one('sale.order', string='Sale Order', index=True, required=True, ondelete='cascade') - purchase_order_id = fields.Many2one('purchase.order', string='SO') - purchase_line_id = fields.Many2one('purchase.order.line', string='SO Line') + purchase_order_id = fields.Many2one('purchase.order', string='PO') + purchase_line_id = fields.Many2one('purchase.order.line', string='PO Line') product_id = fields.Many2one('product.product', string='Product') qty_so = fields.Float(string='Qty SO') qty_po = fields.Float(string='Qty PO') -- cgit v1.2.3 From 5eabba4b2a8a3e1ec233f60c6aa7a0fa7414fd51 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 27 Dec 2023 13:37:37 +0700 Subject: alert if so have matches po --- indoteknik_custom/models/automatic_purchase.py | 29 ++++++++++++-------------- indoteknik_custom/models/purchasing_job.py | 3 +-- 2 files changed, 14 insertions(+), 18 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index 5ab05aed..ed35835e 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -46,24 +46,21 @@ class AutomaticPurchase(models.Model): return result def validate_so_is_po(self): - #TODO user cant create po if in so exist matches po - for line in self.purchase_lines: - # Assuming that 'order_sales_match_line' is a field on the model 'sale.order' - sale_id = self.env['sale.order'].search([ - ('id', '=', line.sale_id.id), - ('order_sales_match_line', '!=', False) - ]) + # TODO user can't create po if in so exist matches po + sale_ids = self.env['automatic.purchase.sales.match'].search([ + ('automatic_purchase_id', '=', self.id), + ]) - # Check if there are multiple records - if len(sale_id) > 1: - raise UserError('Ada SO Yang sudah create PO, berikut SO nya: %s' % [sale.name for sale in sale_id]) - - # Check if there is exactly one record - if sale_id: - # Access the first record in the set - sale_id = sale_id[0] - raise UserError('Ada SO Yang sudah create PO, berikut SO nya: %s' % [sale.name for sale in sale_id]) + names = [] + for sale in sale_ids: + names.append(sale.sale_id.name) + + count = len(sale_ids) + if count > 0: + raise UserError('Ada sekitar %s SO Yang sudah create PO, berikut SO nya: %s' % (count, ', '.join(names))) + + def create_po_from_automatic_purchase(self): if not self.purchase_lines: raise UserError('Tidak ada Lines, belum bisa create PO') diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index 09c202d9..7ac678c4 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -18,13 +18,12 @@ class PurchasingJob(models.Model): incoming = fields.Float(string="Incoming") outgoing = fields.Float(string="Outgoing") action = fields.Char(string="Status") - is_po = fields.Boolean(string='Is PO') def init(self): tools.drop_view_if_exists(self.env.cr, self._table) self.env.cr.execute(""" CREATE OR REPLACE VIEW %s AS ( - select product_id as id, product_id, brand, item_code, product, onhand, incoming, outgoing, action + select product_id as id, product_id, brand, item_code, product, onhand, incoming, outgoing, action from v_procurement_monitoring_by_product where action = 'kurang beli' ) -- cgit v1.2.3 From bacd84a9895031fdd5ce2f7ec0074f1bcd9e0a9a Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 2 Jan 2024 15:44:20 +0700 Subject: create new window for sale detail qty_po get from matches so on po --- indoteknik_custom/models/__init__.py | 1 + indoteknik_custom/models/automatic_purchase.py | 2 +- .../models/sale_monitoring_detail_v2.py | 68 ++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 indoteknik_custom/models/sale_monitoring_detail_v2.py (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index fb986c0d..f1d0a6cf 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -98,3 +98,4 @@ from . import purchasing_job from . import purchasing_job_multi_update from . import purchase_order_sales_match from . import sales_order_purchase_match +from . import sale_monitoring_detail_v2 \ No newline at end of file diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index ed35835e..d0bbdb1e 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -67,7 +67,7 @@ class AutomaticPurchase(models.Model): if self.is_po: raise UserError('Sudah pernah di create PO') - self.validate_so_is_po() + # self.validate_so_is_po() current_time = datetime.now() vendor_ids = self.env['automatic.purchase.line'].read_group( diff --git a/indoteknik_custom/models/sale_monitoring_detail_v2.py b/indoteknik_custom/models/sale_monitoring_detail_v2.py new file mode 100644 index 00000000..1aec2bef --- /dev/null +++ b/indoteknik_custom/models/sale_monitoring_detail_v2.py @@ -0,0 +1,68 @@ +from odoo import fields, models, api, tools +import logging + +_logger = logging.getLogger(__name__) + + +class SaleMonitoringDetailV2(models.Model): + _name = 'sale.monitoring.detail.v2' + _auto = False + _rec_name = 'sale_order_id' + + id = fields.Integer() + sale_order_id = fields.Many2one("sale.order", string="Sale Order") + partner_id = fields.Many2one("res.partner", string="Customer") + user_id = fields.Many2one("res.users", string="Salesperson") + product_id = fields.Many2one("product.product", string="Product") + qty_so = fields.Integer(string="Qty SO") + qty_po = fields.Integer(string="Qty PO") + qty_po_received = fields.Integer(string="Qty PO Received") + qty_so_delivered = fields.Integer(string="Qty SO Delivered") + qty_so_invoiced = fields.Integer(string="Qty SO Invoiced") + date_order = fields.Datetime(string="Date Order") + status = fields.Char(string="Status") + qty_reserved = fields.Integer(string="Qty Reserved") + note = fields.Char(string="Note") + + def init(self): + tools.drop_view_if_exists(self.env.cr, self._table) + self.env.cr.execute(""" + CREATE OR REPLACE VIEW %s AS ( + SELECT + *, + CASE + when qty_so_delivered = qty_so then 'Delivered' + when qty_reserved >= qty_so then 'Siap kirim' + when qty_po + qty_reserved - qty_po_received < qty_so then 'Belum/Kurang PO' + when qty_po_received = 0 then 'Belum terima' + when qty_po_received < qty_po then 'Terima sebagian' + when qty_so_invoiced = qty_so then 'Invoiced' + END AS status + FROM + ( + SELECT + sol.id AS id, + so.id AS sale_order_id, + so.partner_id as partner_id, + so.user_id, + p.id AS product_id, + sol.product_uom_qty AS qty_so, + sol.qty_delivered AS qty_so_delivered, + get_qty_to_delivered(sol.id) as qty_to_delivered, + sol.qty_invoiced AS qty_so_invoiced, + so.date_order AS date_order, + get_qty_po_matches_so(so.id, sol.product_id) AS qty_po, + get_qty_received(so.id, sol.product_id) AS qty_po_received, + get_qty_reserved(so.id, sol.product_id) as qty_reserved, + sol.note_procurement as note + FROM sale_order so + JOIN sale_order_line sol ON sol.order_id = so.id + JOIN product_product p ON p.id = sol.product_id + JOIN product_template pt ON pt.id = p.product_tmpl_id + WHERE pt.type IN ('consu','product') + AND so.state IN ('sale','done') + AND so.create_date >= '2022-08-10' + and so.so_status not in('terproses') + ) a + ) + """ % self._table) -- cgit v1.2.3 From 98f44d62a1e818875911a29cb7a119907e5e2af1 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 5 Feb 2024 14:49:10 +0700 Subject: automatic purchase change request --- indoteknik_custom/models/automatic_purchase.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index d0bbdb1e..4051c093 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -283,7 +283,7 @@ class AutomaticPurchase(models.Model): purchase_pricelist = self.env['purchase.pricelist'].search(domain, order=orderby, limit=1) vendor_id = purchase_pricelist.vendor_id - price = self._get_valid_purchase_price(purchase_pricelist) + price = self._get_vaokelid_purchase_price(purchase_pricelist) last_po_line = self.env['purchase.order.line'].search([('product_id', '=', job.product_id.id), ('order_id.state', '=', 'done')], order='id desc', limit=1) self.env['automatic.purchase.line'].create([{ @@ -460,3 +460,19 @@ class AutomaticPurchaseSalesMatch(models.Model): product_id = fields.Many2one('product.product', string='Product') qty_so = fields.Float(string='Qty SO') qty_po = fields.Float(string='Qty PO') + + +class SyncPurchasingJob(models.Model): + _name = 'sync.purchasing.job' + _auto = False + _rec_name = 'product_id' + + automatic_purchase_id = fields.Many2one('automatic.purchase', string='Ref', required=True, ondelete='cascade', index=True, copy=False) + product_id = fields.Many2one('product.product', string="Product") + brand = fields.Char(string='Brand') + item_code = fields.Char(string='Item Code') + product = fields.Char(string='Product Name') + onhand = fields.Float(string='OnHand') + incoming = fields.Float(string="Incoming") + outgoing = fields.Float(string="Outgoing") + action = fields.Char(string="Status") -- cgit v1.2.3 From bc28a47b83eb481f52c89b0f0fa5a13a851fbc21 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 6 Feb 2024 14:08:16 +0700 Subject: new table sync purchasing job and fix bug duplicate product --- indoteknik_custom/models/automatic_purchase.py | 67 ++++++++++++++++++-------- 1 file changed, 46 insertions(+), 21 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index 4051c093..4161d895 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -26,6 +26,7 @@ class AutomaticPurchase(models.Model): ('reordering', 'Reordering Rule'), ], string='Type', tracking=3) sales_match = fields.One2many('automatic.purchase.sales.match', 'automatic_purchase_id', string='SO Matches', auto_join=True) + purchasing_job_match = fields.One2many('sync.purchasing.job', 'automatic_purchase_id', string='Purchasing Job Matches', auto_join=True) total_qty_line = fields.Float(string='Total Qty Line', compute='_compute_total_qty') total_qty_so = fields.Float(string='Total Qty SO', compute='_compute_total_qty') @@ -283,7 +284,7 @@ class AutomaticPurchase(models.Model): purchase_pricelist = self.env['purchase.pricelist'].search(domain, order=orderby, limit=1) vendor_id = purchase_pricelist.vendor_id - price = self._get_vaokelid_purchase_price(purchase_pricelist) + price = self._get_valid_purchase_price(purchase_pricelist) last_po_line = self.env['purchase.order.line'].search([('product_id', '=', job.product_id.id), ('order_id.state', '=', 'done')], order='id desc', limit=1) self.env['automatic.purchase.line'].create([{ @@ -302,6 +303,7 @@ class AutomaticPurchase(models.Model): _logger.info('Create Automatic Purchase Line %s' % job.product_id.name) self.notification = "Automatic PO Created %s Lines" % count self._create_sales_matching() + self._create_sync_purchasing_job(jobs) print(1) def _create_sales_matching(self): @@ -311,24 +313,47 @@ class AutomaticPurchase(models.Model): ] sales = self.env['v.sales.outstanding'].search(domain) for sale in sales: - if line.qty_purchase > sale.outgoing: - qty_po = sale.outgoing - else: - qty_po = line.qty_purchase - 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': qty_po, - }]) - print(1) + existing_match = self.env['automatic.purchase.sales.match'].search([ + ('automatic_purchase_id', '=', self.id), + ('sale_id', '=', sale.sale_id.id), + ]) + + if not existing_match: + if line.qty_purchase > sale.outgoing: + qty_po = sale.outgoing + else: + qty_po = line.qty_purchase + + 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': qty_po, + }]) + + print(1) + + def _create_sync_purchasing_job(self, jobs): + date = datetime.utcnow() + for line in jobs: + self.env['sync.purchasing.job'].create([{ + 'automatic_purchase_id': self.id, + 'brand': line.brand, + 'item_code': line.item_code, + 'product_id': line.product_id.id, + 'onhand': line.onhand, + 'incoming': line.incoming, + 'outgoing': line.outgoing, + 'action': line.action, + 'date': date + }]) def generate_automatic_purchase(self): # for reordering rule only @@ -464,8 +489,7 @@ class AutomaticPurchaseSalesMatch(models.Model): class SyncPurchasingJob(models.Model): _name = 'sync.purchasing.job' - _auto = False - _rec_name = 'product_id' + _order = 'automatic_purchase_id, id' automatic_purchase_id = fields.Many2one('automatic.purchase', string='Ref', required=True, ondelete='cascade', index=True, copy=False) product_id = fields.Many2one('product.product', string="Product") @@ -476,3 +500,4 @@ class SyncPurchasingJob(models.Model): incoming = fields.Float(string="Incoming") outgoing = fields.Float(string="Outgoing") action = fields.Char(string="Status") + date = fields.Datetime(string="Date Sync") -- cgit v1.2.3 From edb3c1c80931078d40a8f56149aeca9efdcdc07d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 6 Feb 2024 15:53:19 +0700 Subject: change request purchasing job --- indoteknik_custom/models/automatic_purchase.py | 28 +++++++++++++++----------- 1 file changed, 16 insertions(+), 12 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index 4161d895..94dd9cdf 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -284,7 +284,7 @@ class AutomaticPurchase(models.Model): purchase_pricelist = self.env['purchase.pricelist'].search(domain, order=orderby, limit=1) vendor_id = purchase_pricelist.vendor_id - price = self._get_valid_purchase_price(purchase_pricelist) + price, taxes = self._get_valid_purchase_price(purchase_pricelist) last_po_line = self.env['purchase.order.line'].search([('product_id', '=', job.product_id.id), ('order_id.state', '=', 'done')], order='id desc', limit=1) self.env['automatic.purchase.line'].create([{ @@ -294,6 +294,7 @@ class AutomaticPurchase(models.Model): 'qty_available': qty_available, 'partner_id': vendor_id.id, 'last_price': price, + 'taxes_id': taxes, 'subtotal': qty_purchase * price, 'last_order_id': last_po_line.order_id.id, 'last_orderline_id': last_po_line.id, @@ -414,17 +415,19 @@ class AutomaticPurchase(models.Model): self.notification = "Automatic PO Created %s Lines" % count def _get_valid_purchase_price(self, purchase_price): - p_price = 0 - if purchase_price.system_price > 0 and purchase_price.product_price > 0: - if purchase_price.human_last_update > purchase_price.system_last_update: - p_price = purchase_price.product_price - else: - p_price = purchase_price.system_price - elif purchase_price.system_price > 0 and purchase_price.product_price == 0: - p_price = purchase_price.system_price - elif purchase_price.system_price == 0 and purchase_price.product_price > 0: - p_price = purchase_price.product_price - return p_price + price = 0 + taxes = None + human_last_update = purchase_price.human_last_update or datetime.min + system_last_update = purchase_price.system_last_update or datetime.min + + if system_last_update > human_last_update: + price = purchase_price.system_price + taxes = purchase_price.taxes_system_id.id + else: + price = purchase_price.product_price + taxes = purchase_price.taxes_product_id.id + + return price, taxes class AutomaticPurchaseLine(models.Model): @@ -447,6 +450,7 @@ class AutomaticPurchaseLine(models.Model): current_po_id = fields.Many2one('purchase.order', string='Current') current_po_line_id = fields.Many2one('purchase.order.line', string='Current Line') brand_id = fields.Many2one('x_manufactures', string='Brand') + taxes_id = fields.Many2one('account.tax', string='Taxes') @api.onchange('last_price', 'qty_purchase') def _calculate_subtotal(self): -- cgit v1.2.3 From c76e3d6126c3d595a6c3c5d6802a9f5e7015f378 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 9 Feb 2024 09:32:52 +0700 Subject: change request purchasing job --- indoteknik_custom/models/automatic_purchase.py | 30 ++++++++++++++++++++------ indoteknik_custom/models/purchasing_job.py | 2 +- 2 files changed, 25 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index 5919467f..713853e9 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -13,14 +13,14 @@ class AutomaticPurchase(models.Model): _rec_name = 'number' number = fields.Char(string='Document No', index=True, copy=False, readonly=True) - date_doc = fields.Date(string='Date', required=True, help='Isi tanggal hari ini') + date_doc = fields.Date(string='Date', readonly=True, help='Isi tanggal hari ini') description = fields.Char(string='Description', help='bebas isi nya apa') purchase_lines = fields.One2many('automatic.purchase.line', 'automatic_purchase_id', string='Lines', auto_join=True) notification = fields.Char(string='Notification') is_po = fields.Boolean(string='Is PO') purchase_match = fields.One2many('automatic.purchase.match', 'automatic_purchase_id', string='PO Matches', auto_join=True) vendor_id = fields.Many2one('res.partner', string='Vendor', help='boleh kosong, jika diisi, maka hanya keluar data untuk vendor tersebut') - responsible_id = fields.Many2one('res.users', string='Responsible', required=True) + responsible_id = fields.Many2one('res.users', string='Responsible', readonly=True) apo_type = fields.Selection([ ('regular', 'Regular Fulfill SO'), ('reordering', 'Reordering Rule'), @@ -63,6 +63,13 @@ class AutomaticPurchase(models.Model): def create_po_from_automatic_purchase(self): + po = self.env['purchase.order'].search([ + ('state', '=', 'draft') + ]) + + if po: + raise UserError('Ada PO yang statusnya draft, proses dulu') + if not self.purchase_lines: raise UserError('Tidak ada Lines, belum bisa create PO') if self.is_po: @@ -152,10 +159,15 @@ class AutomaticPurchase(models.Model): auto_purchase_line = self.env['automatic.purchase.line'] + last_po = self.env['purchase.order'].search([ + ('partner_id', '=', vendor_id), + ('state', '=', 'done'), + ], order='id desc', limit=1) + param_header = { 'partner_id': vendor_id, 'currency_id': 12, - 'user_id': self.env.user.id, + 'user_id': last_po.user_id.id, 'company_id': 1, # indoteknik dotcom gemilang 'picking_type_id': 28, # indoteknik bandengan receipts 'date_order': current_time, @@ -264,16 +276,20 @@ class AutomaticPurchase(models.Model): raise UserError('Vendor tidak dapat diisi jika Regular Fulfill SO') if self.purchase_lines: raise UserError('Sudah digenerate sebelumnya, hapus line terlebih dahulu') + + self.responsible_id = self.env.user.id + self.date_doc = datetime.utcnow() + #TODO must add order by for fifo mechanism #change the view of v.purchasing.job and use order by in query query = [ - ('action', '=', 'kurang beli') + ('action', '=', 'kurang') ] jobs = self.env['v.purchasing.job'].search(query) count = 0 for job in jobs: - qty_purchase = job.outgoing - job.onhand + job.incoming - qty_available = job.onhand + job.incoming - job.outgoing + qty_purchase = job.outgoing - (job.onhand + job.incoming) + qty_available = (job.onhand + job.incoming) - job.outgoing domain = [ ('product_id.id', '=', job.product_id.id), @@ -361,6 +377,8 @@ class AutomaticPurchase(models.Model): if self.purchase_lines: raise UserError('Sudah digenerate sebelumnya, hapus line terlebih dahulu') + self.responsible_id = self.env.user.id + query = [ ('product_min_qty', '>', 0), ('product_id.x_manufacture.user_id.id', '=', self.responsible_id.id) diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index 7ac678c4..4afb3e03 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -25,7 +25,7 @@ class PurchasingJob(models.Model): CREATE OR REPLACE VIEW %s AS ( select product_id as id, product_id, brand, item_code, product, onhand, incoming, outgoing, action from v_procurement_monitoring_by_product - where action = 'kurang beli' + where action = 'kurang' ) """ % self._table) -- cgit v1.2.3 From 0738a192409687790c16c757f85fe440cb1f377d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 12 Feb 2024 08:27:42 +0700 Subject: change request purchasing job --- indoteknik_custom/models/automatic_purchase.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index 713853e9..da845645 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -63,13 +63,6 @@ class AutomaticPurchase(models.Model): def create_po_from_automatic_purchase(self): - po = self.env['purchase.order'].search([ - ('state', '=', 'draft') - ]) - - if po: - raise UserError('Ada PO yang statusnya draft, proses dulu') - if not self.purchase_lines: raise UserError('Tidak ada Lines, belum bisa create PO') if self.is_po: @@ -270,6 +263,11 @@ class AutomaticPurchase(models.Model): sales_order_purchase_match = self.env['sales.order.purchase.match'].create([matches_po_line]) def generate_regular_purchase(self): + po = self.env['purchase.order'].search([ + ('state', '=', 'draft') + ]) + if po: + raise UserError('Ada PO yang statusnya draft, proses dulu') if self.apo_type == 'reordering': raise UserError('Tombol ini hanya untuk Regular Fulfill SO') if self.vendor_id: -- cgit v1.2.3 From d3e3a8abf0ae382442c16a7ac6091c7bb872313f Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 13 Mar 2024 16:24:13 +0700 Subject: change request purchasing job --- indoteknik_custom/models/__init__.py | 1 + indoteknik_custom/models/purchasing_job.py | 48 +++++++++++++++++++++- .../models/report_stock_forecasted.py | 27 ++++++++---- indoteknik_custom/models/sale_monitoring_detail.py | 12 +++--- indoteknik_custom/models/sale_order.py | 1 + indoteknik_custom/models/sale_order_line.py | 10 ++--- .../models/sales_order_fullfillment.py | 15 +++++++ 7 files changed, 93 insertions(+), 21 deletions(-) create mode 100644 indoteknik_custom/models/sales_order_fullfillment.py (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 9d8b9bea..5af30414 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -109,3 +109,4 @@ from . import report_logbook_sj from . import role_permission from . import cust_commision from . import report_stock_forecasted +from . import sales_order_fullfillment diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index 4afb3e03..b3c25256 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -1,5 +1,6 @@ -from odoo import fields, models, api, tools +from odoo import fields, models, api, tools, _ import logging +from datetime import datetime _logger = logging.getLogger(__name__) @@ -39,9 +40,54 @@ class PurchasingJob(models.Model): def generate_request_po(self): # print(1) # TODO create document automatic purchase + + current_time = datetime.utcnow() + + automatic_purchase = self.env['automatic.purchase'].create([{ + 'apo_type': 'regular', + 'date_doc': current_time, + }]) + count = 0 for job in self: print(job.product_id.name) + qty_purchase = job.outgoing - (job.onhand + job.incoming) + qty_available = (job.onhand + job.incoming) - job.outgoing + + domain = [ + ('product_id.id', '=', job.product_id.id), + ] + orderby = 'count_trx_po desc, count_trx_po_vendor desc' + purchase_pricelist = self.env['purchase.pricelist'].search(domain, order=orderby, limit=1) + vendor_id = purchase_pricelist.vendor_id + price, taxes = automatic_purchase._get_valid_purchase_price(purchase_pricelist) + last_po_line = self.env['purchase.order.line'].search([('product_id', '=', job.product_id.id), ('order_id.state', '=', 'done')], order='id desc', limit=1) + + self.env['automatic.purchase.line'].create([{ + 'automatic_purchase_id': automatic_purchase.id, + 'product_id': job.product_id.id, + 'qty_purchase': qty_purchase, + 'qty_available': qty_available, + 'partner_id': vendor_id.id, + 'last_price': price, + 'taxes_id': taxes, + 'subtotal': qty_purchase * price, + 'last_order_id': last_po_line.order_id.id, + 'last_orderline_id': last_po_line.id, + 'brand_id': job.product_id.product_tmpl_id.x_manufacture.id + }]) + automatic_purchase._create_sales_matching() + automatic_purchase._create_sync_purchasing_job(job) + count += 1 + _logger.info('Create Automatic Purchase Line %s' % job.product_id.name) + return { + 'name': _('Automatic Purchase'), + 'view_mode': 'tree,form', + 'res_model': 'automatic.purchase', + 'target': 'current', + 'type': 'ir.actions.act_window', + 'domain': [('id', '=', automatic_purchase.id)], + } class OutstandingSales(models.Model): _name = 'v.sales.outstanding' diff --git a/indoteknik_custom/models/report_stock_forecasted.py b/indoteknik_custom/models/report_stock_forecasted.py index 48a17095..7ae93fda 100644 --- a/indoteknik_custom/models/report_stock_forecasted.py +++ b/indoteknik_custom/models/report_stock_forecasted.py @@ -6,26 +6,37 @@ 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: - product_id = line.get('product', {}).get('id') - query = [('product_id', '=', product_id)] document_out = line.get('document_out') order_id = document_out.id if document_out else None + product_id = line.get('product', {}).get('id') + query = [('product_id', '=', product_id)] if order_id: result = self._calculate_result(line) - result_dict.setdefault(order_id, []).append(result) + quantity = line.get('quantity', 0) + result_dict.setdefault(order_id, []).append((result, quantity)) for order_id, results in result_dict.items(): sale_order_lines = self.env['sale.order.line'].search([('order_id', '=', order_id)]) + sales_order = self.env['sale.order'].browse(order_id) + + for result, quantity in results: + fullfillment = self.env['sales.order.fullfillment'].search([('sales_order_id', '=', sales_order.id), ('product_id', '=', product_id)]) + if fullfillment: + fullfillment.reserved_from = result + fullfillment.qty_fullfillment = quantity + continue + + fullfillment.create({ + 'sales_order_id': sales_order.id, + 'product_id': product_id, + 'reserved_from': result, + 'qty_fullfillment': quantity, + }) - concatenated_result = ' ,'.join(results) - - for sale_order_line in sale_order_lines: - sale_order_line.reserved_from = concatenated_result return lines diff --git a/indoteknik_custom/models/sale_monitoring_detail.py b/indoteknik_custom/models/sale_monitoring_detail.py index 8c35b1cc..ec5e4711 100755 --- a/indoteknik_custom/models/sale_monitoring_detail.py +++ b/indoteknik_custom/models/sale_monitoring_detail.py @@ -24,16 +24,16 @@ class SaleMonitoringDetail(models.Model): qty_reserved = fields.Integer(string="Qty Reserved") note = fields.Char(string="Note") vendor_id = fields.Many2one('res.partner', string='Vendor') + fullfillment = fields.Char(string="Fullfillment", compute='compute_fullfillment') - def _compute_vendor(self): + def compute_fullfillment(self): for r in self: - sale_lines = self.env['sale.order.line'].search([ - ('order_id', '=', r.sale_order_id.id), + fullfillment = self.env['sales.order.fullfillment'].search([ + ('sales_order_id', '=', r.sale_order_id.id), ('product_id', '=', r.product_id.id), - ]) + ], limit=1) - for line in sale_lines: - r.vendor_id = line.vendor_id.id + r.fullfillment = fullfillment.reserved_from def init(self): tools.drop_view_if_exists(self.env.cr, self._table) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 8b789976..32ffc964 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -9,6 +9,7 @@ _logger = logging.getLogger(__name__) class SaleOrder(models.Model): _inherit = "sale.order" + fullfillment_line = fields.One2many('sales.order.fullfillment', 'sales_order_id', string='Fullfillment') order_sales_match_line = fields.One2many('sales.order.purchase.match', 'sales_order_id', string='Purchase Match Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True) total_margin = fields.Float('Total Margin', compute='_compute_total_margin', help="Total Margin in Sales Order Header") total_percent_margin = fields.Float('Total Percent Margin', compute='_compute_total_percent_margin', help="Total % Margin in Sales Order Header") diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index a140468c..95c9cdfa 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -68,14 +68,12 @@ class SaleOrderLine(models.Model): line.qty_reserved = reserved_qty if reserved_qty > 0: - line._compute_reserved_from() - + self._compute_reserved_from() def _compute_reserved_from(self): - for line in self: - # continue - report_stock_forecasted = self.env['report.stock.report_product_product_replenishment'] - report_stock_forecasted._get_report_data(False, [line.product_id.id]) + for line in self: + report_stock_forecasted = self.env['report.stock.report_product_product_replenishment'] + report_stock_forecasted._get_report_data(False, [line.product_id.id]) def _compute_vendor_subtotal(self): for line in self: diff --git a/indoteknik_custom/models/sales_order_fullfillment.py b/indoteknik_custom/models/sales_order_fullfillment.py new file mode 100644 index 00000000..db6b7369 --- /dev/null +++ b/indoteknik_custom/models/sales_order_fullfillment.py @@ -0,0 +1,15 @@ +from odoo import fields, models, api, _ +from odoo.exceptions import AccessError, UserError, ValidationError +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT +import logging + +_logger = logging.getLogger(__name__) + + +class SalesOrderFullfillment(models.Model): + _name = 'sales.order.fullfillment' + + sales_order_id = fields.Many2one('sale.order', string='Sale Order', index=True, required=True, ondelete='cascade') + product_id = fields.Many2one('product.product', string='Product') + reserved_from = fields.Char(string='Reserved From', copy=False) + qty_fullfillment = fields.Float(string='Qty') \ No newline at end of file -- cgit v1.2.3 From a774c1bd0b2dc1ee27a08b6b4da7162bdb7a9365 Mon Sep 17 00:00:00 2001 From: Rafi Zadanly Date: Fri, 15 Mar 2024 11:12:46 +0700 Subject: Fix compute fulfillment feature in sale order --- indoteknik_custom/models/report_stock_forecasted.py | 9 +-------- indoteknik_custom/models/sale_order.py | 11 +++++++++++ indoteknik_custom/models/sale_order_line.py | 3 --- indoteknik_custom/models/sales_order_fullfillment.py | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/report_stock_forecasted.py b/indoteknik_custom/models/report_stock_forecasted.py index 7ae93fda..44bdfe3e 100644 --- a/indoteknik_custom/models/report_stock_forecasted.py +++ b/indoteknik_custom/models/report_stock_forecasted.py @@ -20,17 +20,10 @@ class ReplenishmentReport(models.AbstractModel): result_dict.setdefault(order_id, []).append((result, quantity)) for order_id, results in result_dict.items(): - sale_order_lines = self.env['sale.order.line'].search([('order_id', '=', order_id)]) sales_order = self.env['sale.order'].browse(order_id) for result, quantity in results: - fullfillment = self.env['sales.order.fullfillment'].search([('sales_order_id', '=', sales_order.id), ('product_id', '=', product_id)]) - if fullfillment: - fullfillment.reserved_from = result - fullfillment.qty_fullfillment = quantity - continue - - fullfillment.create({ + self.env['sales.order.fullfillment'].create({ 'sales_order_id': sales_order.id, 'product_id': product_id, 'reserved_from': result, diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 32ffc964..d046bb00 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -86,6 +86,17 @@ class SaleOrder(models.Model): picking_iu_id = fields.Many2one('stock.picking', 'Picking IU') helper_by_id = fields.Many2one('res.users', 'Helper By') eta_date = fields.Datetime(string='ETA Date', copy=False, compute='_compute_eta_date') + compute_fullfillment = fields.Boolean(string='Compute Fullfillment', compute="_compute_fullfillment") + + def _compute_fullfillment(self): + for rec in self: + for fullfillment in rec.fullfillment_line: + fullfillment.sales_order_id = False + + for line in rec.order_line: + line._compute_reserved_from() + + rec.compute_fullfillment = True def _compute_eta_date(self): max_leadtime = 0 diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 95c9cdfa..5b5b35d6 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -67,9 +67,6 @@ class SaleOrderLine(models.Model): reserved_qty = sum(move.product_uom_qty for move in stock_moves) line.qty_reserved = reserved_qty - if reserved_qty > 0: - self._compute_reserved_from() - def _compute_reserved_from(self): for line in self: report_stock_forecasted = self.env['report.stock.report_product_product_replenishment'] diff --git a/indoteknik_custom/models/sales_order_fullfillment.py b/indoteknik_custom/models/sales_order_fullfillment.py index db6b7369..97e6d5c2 100644 --- a/indoteknik_custom/models/sales_order_fullfillment.py +++ b/indoteknik_custom/models/sales_order_fullfillment.py @@ -9,7 +9,7 @@ _logger = logging.getLogger(__name__) class SalesOrderFullfillment(models.Model): _name = 'sales.order.fullfillment' - sales_order_id = fields.Many2one('sale.order', string='Sale Order', index=True, required=True, ondelete='cascade') + sales_order_id = fields.Many2one('sale.order', string='Sale Order') product_id = fields.Many2one('product.product', string='Product') reserved_from = fields.Char(string='Reserved From', copy=False) qty_fullfillment = fields.Float(string='Qty') \ No newline at end of file -- cgit v1.2.3 From 1d1544c9450cafe32bad7d6c7b723f14b397fe2c Mon Sep 17 00:00:00 2001 From: Rafi Zadanly Date: Fri, 15 Mar 2024 11:19:12 +0700 Subject: Update compute fulfillment to unlink old data --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index d046bb00..0f075bf8 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -91,7 +91,7 @@ class SaleOrder(models.Model): def _compute_fullfillment(self): for rec in self: for fullfillment in rec.fullfillment_line: - fullfillment.sales_order_id = False + fullfillment.unlink() for line in rec.order_line: line._compute_reserved_from() -- cgit v1.2.3 From a05da3fad1855cbf2ce4cc7645f1fda79cae037c Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 18 Mar 2024 11:46:00 +0700 Subject: Not Available -> Unfulfilled --- indoteknik_custom/models/report_stock_forecasted.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/report_stock_forecasted.py b/indoteknik_custom/models/report_stock_forecasted.py index 44bdfe3e..92da13d5 100644 --- a/indoteknik_custom/models/report_stock_forecasted.py +++ b/indoteknik_custom/models/report_stock_forecasted.py @@ -44,6 +44,6 @@ class ReplenishmentReport(models.AbstractModel): else: return 'Free Stock' else: - return 'Not Available' + return 'Unfulfilled' -- cgit v1.2.3 From b2377426bec8aa334277aac48b0b25f0dfac420f Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 25 Mar 2024 13:07:44 +0700 Subject: purchasing job feedback --- indoteknik_custom/models/__init__.py | 1 + indoteknik_custom/models/automatic_purchase.py | 13 +++++ indoteknik_custom/models/purchase_order.py | 58 ++++++++++++++++++++++ indoteknik_custom/models/purchasing_job.py | 46 +++++++++++++---- .../models/purchasing_job_multi_update.py | 29 +++++++---- indoteknik_custom/models/purchasing_job_state.py | 17 +++++++ indoteknik_custom/models/sale_order_line.py | 2 +- .../models/sales_order_fullfillment.py | 13 ++++- 8 files changed, 156 insertions(+), 23 deletions(-) create mode 100644 indoteknik_custom/models/purchasing_job_state.py (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 5af30414..a7ab1089 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -94,6 +94,7 @@ from . import account_bank_statement from . import stock_warehouse_orderpoint from . import commision from . import sale_advance_payment_inv +from . import purchasing_job_state from . import purchasing_job from . import purchasing_job_multi_update from . import purchase_order_sales_match diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index da845645..3e2d31e7 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -164,6 +164,7 @@ class AutomaticPurchase(models.Model): 'company_id': 1, # indoteknik dotcom gemilang 'picking_type_id': 28, # indoteknik bandengan receipts 'date_order': current_time, + 'from_apo': True, 'note_description': 'Automatic PO' } @@ -192,8 +193,19 @@ class AutomaticPurchase(models.Model): limit=PRODUCT_PER_PO ) + lines = auto_purchase_line.search( + domain, + offset=i * PRODUCT_PER_PO, + limit=PRODUCT_PER_PO + ) + + for line in lines: product = line.product_id + sales_match = self.env['automatic.purchase.sales.match'].search([ + ('automatic_purchase_id', '=', self.id), + ('product_id', '=', product.id), + ]) param_line = { 'order_id': new_po.id, 'product_id': product.id, @@ -202,6 +214,7 @@ class AutomaticPurchase(models.Model): 'suggest': product._get_po_suggest(line.qty_purchase), 'product_uom_qty': line.qty_purchase, 'price_unit': line.last_price, + # 'so_line_id': [sales.sale_line_id.id for sales in sales_match], } new_po_line = self.env['purchase.order.line'].create([param_line]) line.current_po_id = new_po.id diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 99d79072..1cdf5094 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -53,6 +53,7 @@ class PurchaseOrder(models.Model): responsible_ids = fields.Many2many('res.users', string='Responsibles', compute='_compute_responsibles') status_paid_cbd = fields.Boolean(string='Paid Status', tracking=3, help='Field ini diisi secara manual oleh Finance AP dan hanya untuk status PO CBD') revisi_po = fields.Boolean(string='Revisi', tracking=3) + from_apo = fields.Boolean(string='From APO') @api.model def action_multi_cancel(self): @@ -442,6 +443,10 @@ class PurchaseOrder(models.Model): return res def compute_total_margin(self): + if self.from_apo: + self.compute_total_margin_from_apo() + return + sum_so_margin = sum_sales_price = sum_margin = 0 for line in self.order_line: sale_order_line = line.so_line_id @@ -475,6 +480,59 @@ class PurchaseOrder(models.Model): self.total_so_margin = 0 self.total_so_percent_margin = 0 + def compute_total_margin_from_apo(self): + purchase_price_dict = {} + + for lines in self.order_line: + product_id = lines.product_id.id + + if product_id not in purchase_price_dict: + purchase_price_dict[product_id] = lines.price_subtotal + + sum_so_margin = sum_sales_price = sum_margin = 0 + for line in self.order_sales_match_line: + sale_order_line = line.sale_line_id + + if not sale_order_line: + sale_order_line = self.env['sale.order.line'].search([ + ('product_id', '=', line.product_id.id), + ('order_id', '=', line.sale_id.id) + ], limit=1, order='price_reduce_taxexcl') + + sum_so_margin += sale_order_line.item_margin + + sales_price = sale_order_line.price_reduce_taxexcl * sale_order_line.product_uom_qty + + if sale_order_line.order_id.shipping_cost_covered == 'indoteknik': + sales_price -= sale_order_line.delivery_amt_line + + if sale_order_line.order_id.fee_third_party > 0: + sales_price -= sale_order_line.fee_third_party_line + + sum_sales_price += sales_price + + product_id = sale_order_line.product_id.id + + purchase_price = purchase_price_dict.get(product_id, 0) + + if line.purchase_order_id.delivery_amount > 0: + purchase_price += line.delivery_amt_line + + real_item_margin = sales_price - purchase_price + sum_margin += real_item_margin + + + if sum_so_margin != 0 and sum_sales_price != 0 and sum_margin != 0: + self.total_so_margin = sum_so_margin + self.total_so_percent_margin = round((sum_so_margin / sum_sales_price), 2) * 100 + self.total_margin = sum_margin + self.total_percent_margin = round((sum_margin / sum_sales_price), 2) * 100 + else: + self.total_margin = 0 + self.total_percent_margin = 0 + self.total_so_margin = 0 + self.total_so_percent_margin = 0 + def compute_amt_total_without_service(self): for order in self: sum_price_total = 0 diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index b3c25256..1c74f0e9 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -12,6 +12,7 @@ class PurchasingJob(models.Model): id = fields.Integer() product_id = fields.Many2one('product.product', string="Product") + vendor_id = fields.Many2one('res.partner', string="Vendor", compute='compute_vendor_id') brand = fields.Char(string='Brand') item_code = fields.Char(string='Item Code') product = fields.Char(string='Product Name') @@ -19,17 +20,47 @@ class PurchasingJob(models.Model): incoming = fields.Float(string="Incoming") outgoing = fields.Float(string="Outgoing") action = fields.Char(string="Status") + status_apo = fields.Selection([ + ('not_apo', 'Belum APO'), + ('apo', 'APO') + ], string='APO?') + + def compute_vendor_id(self): + for sale in self: + domain = [ + ('product_id', '=', sale.product_id.id) + ] + sales = self.env['v.sales.outstanding'].search(domain) + + vendor_id = False + + if sales: + vendor_id = sales[0].sale_line_id.vendor_id.id + + sale.vendor_id = vendor_id def init(self): tools.drop_view_if_exists(self.env.cr, self._table) self.env.cr.execute(""" CREATE OR REPLACE VIEW %s AS ( - select product_id as id, product_id, brand, item_code, product, onhand, incoming, outgoing, action - from v_procurement_monitoring_by_product - where action = 'kurang' + SELECT + pmp.product_id as id, + pmp.product_id, + pmp.brand, + pmp.item_code, + pmp.product, + pmp.onhand, + pmp.incoming, + pmp.outgoing, + pmp.action, + pjs.status_apo AS status_apo + FROM v_procurement_monitoring_by_product pmp + LEFT JOIN purchasing_job_state pjs ON pjs.purchasing_job_id = pmp.product_id + WHERE pmp.action = 'kurang' ) """ % self._table) + def open_form_multi_generate_request_po(self): action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_purchasing_job_multi_update') action['context'] = { @@ -80,14 +111,7 @@ class PurchasingJob(models.Model): automatic_purchase._create_sync_purchasing_job(job) count += 1 _logger.info('Create Automatic Purchase Line %s' % job.product_id.name) - return { - 'name': _('Automatic Purchase'), - 'view_mode': 'tree,form', - 'res_model': 'automatic.purchase', - 'target': 'current', - 'type': 'ir.actions.act_window', - 'domain': [('id', '=', automatic_purchase.id)], - } + return automatic_purchase.id class OutstandingSales(models.Model): _name = 'v.sales.outstanding' diff --git a/indoteknik_custom/models/purchasing_job_multi_update.py b/indoteknik_custom/models/purchasing_job_multi_update.py index a1b79907..e7f19e50 100644 --- a/indoteknik_custom/models/purchasing_job_multi_update.py +++ b/indoteknik_custom/models/purchasing_job_multi_update.py @@ -1,5 +1,6 @@ -from odoo import models, fields +from odoo import models, fields, _ import logging +from odoo.exceptions import AccessError, UserError, ValidationError _logger = logging.getLogger(__name__) @@ -9,14 +10,22 @@ class PurchasingJobMultiUpdate(models.TransientModel): def save_multi_update_purchasing_job(self): product_ids = self._context['product_ids'] - product = self.env['v.purchasing.job'].browse(product_ids) - product.generate_request_po() + products = self.env['v.purchasing.job'].browse(product_ids) + for product in products: + if product.status_apo == 'apo': + raise UserError('Ada Purchase Order yang statusnya APO, proses dulu') + + purchasing_job_state = self.env['purchasing.job.state'] + purchasing_job_state.create({ + 'purchasing_job_id': product.id, + 'status_apo': 'apo', + }) + apo = products.generate_request_po() return { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { - 'title': 'Notification', - 'message': 'Berhasil membuat Automatic Purchase', - 'next': {'type': 'ir.actions.act_window_close'}, - } + 'name': _('Automatic Purchase'), + 'view_mode': 'tree,form', + 'res_model': 'automatic.purchase', + 'target': 'current', + 'type': 'ir.actions.act_window', + 'domain': [('id', '=', apo)], } \ No newline at end of file diff --git a/indoteknik_custom/models/purchasing_job_state.py b/indoteknik_custom/models/purchasing_job_state.py new file mode 100644 index 00000000..57fd3db2 --- /dev/null +++ b/indoteknik_custom/models/purchasing_job_state.py @@ -0,0 +1,17 @@ +from odoo import fields, models, api, tools, _ +import logging +from datetime import datetime + +_logger = logging.getLogger(__name__) + + +class PurchasingJobState(models.Model): + _name = 'purchasing.job.state' + _rec_name = 'purchasing_job_id' + + purchasing_job_id = fields.Many2one('purchasing.job', string='Ref') + status_apo = fields.Selection([ + ('not_apo', 'Belum APO'), + ('apo', 'APO') + ], string='APO?', copy=False) + diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 5b5b35d6..39366028 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -13,7 +13,7 @@ class SaleOrderLine(models.Model): change_default=True, index=True, tracking=1, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]" - ) + ) purchase_price = fields.Float('Purchase', required=True, digits='Product Price', default=0.0) purchase_tax_id = fields.Many2one('account.tax', string='Tax', domain=['|', ('active', '=', False), ('active', '=', True)]) delivery_amt_line = fields.Float('DeliveryAmtLine', compute='compute_delivery_amt_line') diff --git a/indoteknik_custom/models/sales_order_fullfillment.py b/indoteknik_custom/models/sales_order_fullfillment.py index 97e6d5c2..ab416e8d 100644 --- a/indoteknik_custom/models/sales_order_fullfillment.py +++ b/indoteknik_custom/models/sales_order_fullfillment.py @@ -12,4 +12,15 @@ class SalesOrderFullfillment(models.Model): sales_order_id = fields.Many2one('sale.order', string='Sale Order') product_id = fields.Many2one('product.product', string='Product') reserved_from = fields.Char(string='Reserved From', copy=False) - qty_fullfillment = fields.Float(string='Qty') \ No newline at end of file + qty_fullfillment = fields.Float(string='Qty') + user_id = fields.Many2one('res.users', string='Responsible', compute='get_user_id') + + def get_user_id(self): + for rec in self: + po = self.env['purchase.order'].search([('name', '=', rec.reserved_from)], limit=1) + + if po: + rec.user_id = po.user_id.id + else: + rec.user_id = False + -- cgit v1.2.3