summaryrefslogtreecommitdiff
path: root/fixco_custom/models
diff options
context:
space:
mode:
authorAzka Nathan <darizkyfaz@gmail.com>2025-07-28 11:22:49 +0700
committerAzka Nathan <darizkyfaz@gmail.com>2025-07-28 11:22:49 +0700
commitece8f2950b6c8fc8b65def8bc70d347fe7157f96 (patch)
treefdd85bcac414aa2ee968ee6f640b010f5d70289d /fixco_custom/models
parentb48199f0ffefe2ccd43799c06f679f1db96c5334 (diff)
skema buffer stock, cr purchasing job, api cancel so
Diffstat (limited to 'fixco_custom/models')
-rwxr-xr-xfixco_custom/models/__init__.py3
-rw-r--r--fixco_custom/models/automatic_purchase.py27
-rwxr-xr-xfixco_custom/models/detail_order.py17
-rw-r--r--fixco_custom/models/purchasing_job.py169
-rw-r--r--fixco_custom/models/reordering_rule.py94
5 files changed, 235 insertions, 75 deletions
diff --git a/fixco_custom/models/__init__.py b/fixco_custom/models/__init__.py
index e80acd8..297002e 100755
--- a/fixco_custom/models/__init__.py
+++ b/fixco_custom/models/__init__.py
@@ -25,4 +25,5 @@ from . import purchasing_job
from . import stock_move
from . import upload_bills
from . import brands
-from . import product_public_category \ No newline at end of file
+from . import product_public_category
+from . import reordering_rule \ No newline at end of file
diff --git a/fixco_custom/models/automatic_purchase.py b/fixco_custom/models/automatic_purchase.py
index d2d6830..037d65a 100644
--- a/fixco_custom/models/automatic_purchase.py
+++ b/fixco_custom/models/automatic_purchase.py
@@ -108,12 +108,19 @@ class AutomaticPurchase(models.Model):
action['views'] = [(self.env.ref('purchase.purchase_order_form').id, 'form')]
action['res_id'] = self.purchase_order_ids[0].id
return action
+
+ def delete_note_pj(self):
+ self.env['purchasing.job.note'].search([
+ ('product_id', 'in', self.automatic_purchase_lines.mapped('product_id').ids)
+ ]).unlink()
def create_purchase_orders(self):
self.ensure_one()
if not self.automatic_purchase_lines:
raise UserError(_("No purchase lines to process!"))
+ self.delete_note_pj()
+
if self.is_po:
raise UserError(_("Purchase order already created!"))
@@ -162,23 +169,8 @@ class AutomaticPurchase(models.Model):
self.is_po = True
return self.action_view_purchase_orders()
-
-
def _create_purchase_order(self, vendor, sequence, total_chunks, brand_id=None, category_id=None):
- order_name = f"{self.number} - {vendor.name}"
-
- if brand_id:
- brand = self.env['brands'].browse(brand_id)
- order_name += f" - {brand.name}"
-
- if category_id:
- cat = self.env['product.public.category'].browse(category_id)
- order_name += f" - {cat.name}"
-
- if total_chunks > 1:
- order_name += f" ({sequence}/{total_chunks})"
-
return self.env['purchase.order'].create({
'partner_id': vendor.id,
'origin': self.number,
@@ -187,7 +179,6 @@ class AutomaticPurchase(models.Model):
'currency_id': vendor.property_purchase_currency_id.id or self.env.company.currency_id.id,
'automatic_purchase_id': self.id,
'source': self.apo_type,
- 'name': order_name,
})
@@ -377,9 +368,9 @@ class AutomaticPurchaseLine(models.Model):
for line in self:
if line.product_id:
stock_move = self.env['stock.move'].search([
- ('product_id', '=', line.product_id.id),
+ ('product_id', '=', line.product_id.id),
('location_dest_id', '=', 5),
('location_id', '=', 55),
('state', 'in', ['waiting', 'confirmed', 'assigned', 'partially_available'])
- ], limit=1)
+ ])
line.qty_outgoing = sum(move.product_uom_qty for move in stock_move) \ No newline at end of file
diff --git a/fixco_custom/models/detail_order.py b/fixco_custom/models/detail_order.py
index 188424a..e8e87aa 100755
--- a/fixco_custom/models/detail_order.py
+++ b/fixco_custom/models/detail_order.py
@@ -276,13 +276,6 @@ class DetailOrder(models.Model):
# First check if a sale order with this reference already exists
existing_order = self.env['sale.order'].search([('order_reference', '=', order_id)], limit=1)
- if existing_order:
- # If order already exists, just update the references
- self.sale_id = existing_order.id
- self.execute_status = 'already_so'
- return # Exit early since we don't need to create anything
-
- # Handle cancelled orders
if order_status == 'CANCELLED':
external_order_id = json_data.get('data', [{}])[0].get('externalOrderId')
order_id = json_data.get('data', [{}])[0].get('orderId')
@@ -302,10 +295,12 @@ class DetailOrder(models.Model):
picking.action_cancel()
self.sale_id = existing_order.id
self.execute_status = 'cancelled_so_picking'
+ existing_order.action_cancel()
else:
existing_order.action_cancel()
self.sale_id = existing_order.id
self.execute_status = 'cancelled_so'
+
else:
# If no existing SO, create one, then cancel
data = self.prepare_data_so(json_data)
@@ -320,6 +315,14 @@ class DetailOrder(models.Model):
sale_order.action_cancel()
self.execute_status = 'cancelled_so_created'
+
+ return
+
+ if existing_order:
+ # If order already exists, just update the references
+ self.sale_id = existing_order.id
+ self.execute_status = 'already_so'
+ return # Exit early since we don't need to create anything
if order_status != 'PENDING_PAYMENT':
if order_status in ('PARTIALLY_PAID', 'PAID'):
diff --git a/fixco_custom/models/purchasing_job.py b/fixco_custom/models/purchasing_job.py
index 2c7138a..5d37c8c 100644
--- a/fixco_custom/models/purchasing_job.py
+++ b/fixco_custom/models/purchasing_job.py
@@ -6,11 +6,10 @@ class PurchasingJob(models.Model):
_name = 'purchasing.job'
_description = 'Procurement Monitoring by Product'
_auto = False
- _rec_name = 'product'
+ _rec_name = 'product_id'
id = fields.Integer(string='ID', readonly=True)
item_code = fields.Char(string='Item Code')
- product = fields.Char(string='Product')
onhand = fields.Float(string='On Hand')
incoming = fields.Float(string='Incoming')
outgoing = fields.Float(string='Outgoing')
@@ -18,6 +17,30 @@ class PurchasingJob(models.Model):
product_id = fields.Many2one('product.product', string='Product')
vendor_id = fields.Many2one('res.partner', string='Vendor')
brand_id = fields.Many2one('brands', string='Brand')
+ note = fields.Text(string='Note', compute='_compute_note')
+
+ def action_open_note_wizard(self):
+ self.ensure_one()
+ return {
+ 'name': 'Add/Edit Note',
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'purchasing.job.note.wizard',
+ 'view_mode': 'form',
+ 'target': 'new',
+ 'context': {
+ 'default_product_id': self.product_id.id,
+ 'default_note': self.note,
+ }
+ }
+
+
+ def _compute_note(self):
+ for rec in self:
+ note_obj = self.env['purchasing.job.note'].search([
+ ('product_id', '=', rec.product_id.id)
+ ], limit=1)
+ rec.note = note_obj.note if note_obj else ''
+
def create_automatic_purchase(self):
if not self:
@@ -72,54 +95,102 @@ class PurchasingJob(models.Model):
def init(self):
tools.drop_view_if_exists(self.env.cr, self._table)
self.env.cr.execute("""
- CREATE OR REPLACE VIEW %s AS
- SELECT * FROM (
- SELECT
- row_number() OVER () AS id,
- a.item_code,
- a.product,
- a.onhand,
- a.incoming,
- a.outgoing,
- CASE
- WHEN (a.incoming + a.onhand) < a.outgoing THEN 'kurang'
- ELSE 'cukup'
- END AS action,
- a.product_id,
- pp2.vendor_id,
- b.id AS brand_id
- FROM (
- SELECT
- COALESCE(pp.default_code, pt.default_code) AS item_code,
- pt.name AS product,
- get_qty_onhand(pp.id::numeric) AS onhand,
- get_qty_incoming(pp.id::numeric) AS incoming,
- get_qty_outgoing(pp.id::numeric) AS outgoing,
- pp.id AS product_id,
- pp.brand_id
- FROM stock_move sm
- JOIN stock_picking sp ON sp.id = sm.picking_id
- JOIN product_product pp ON pp.id = sm.product_id
- JOIN product_template pt ON pt.id = pp.product_tmpl_id
- WHERE sp.state IN ('draft', 'waiting', 'confirmed', 'assigned')
- AND sp.name LIKE '%%OUT%%'
- AND sm.location_id = 55
- GROUP BY pp.id, pp.default_code, pt.default_code, pt.name, pp.brand_id
- ) a
- LEFT JOIN brands b ON b.id = a.brand_id
- LEFT JOIN LATERAL (
- SELECT vendor_id
- FROM purchase_pricelist
- WHERE product_id = a.product_id
- ORDER BY id ASC
- LIMIT 1
- ) pp2 ON true
- ) final
- WHERE final.action = 'kurang'
+ CREATE OR REPLACE VIEW %s
+ AS SELECT
+ row_number() OVER ()::integer AS id,
+ pmp.item_code,
+ max(pmp.onhand) AS onhand,
+ max(pmp.incoming) AS incoming,
+ max(pmp.outgoing) AS outgoing,
+ pmp.action,
+ pmp.product_id,
+ pmp.brand_id,
+ pmp.vendor_id
+ FROM v_procurement_monitoring_by_product pmp
+ WHERE pmp.action = 'kurang'::text
+ GROUP BY pmp.product_id, pmp.item_code, pmp.action, pmp.brand_id, pmp.vendor_id;
""" % self._table)
- super(PurchasingJob, self).init()
-
-
+ # def init(self):
+ # tools.drop_view_if_exists(self.env.cr, self._table)
+ # self.env.cr.execute("""
+ # CREATE OR REPLACE VIEW %s AS
+ # SELECT * FROM (
+ # SELECT
+ # row_number() OVER () AS id,
+ # a.item_code,
+ # a.product,
+ # a.onhand,
+ # a.incoming,
+ # a.outgoing,
+ # CASE
+ # WHEN (a.incoming + a.onhand) < a.outgoing THEN 'kurang'
+ # ELSE 'cukup'
+ # END AS action,
+ # a.product_id,
+ # pp2.vendor_id,
+ # b.id AS brand_id
+ # FROM (
+ # SELECT
+ # COALESCE(pp.default_code, pt.default_code) AS item_code,
+ # pt.name AS product,
+ # get_qty_onhand(pp.id::numeric) AS onhand,
+ # get_qty_incoming(pp.id::numeric) AS incoming,
+ # get_qty_outgoing(pp.id::numeric) AS outgoing,
+ # pp.id AS product_id,
+ # pp.brand_id
+ # FROM stock_move sm
+ # JOIN stock_picking sp ON sp.id = sm.picking_id
+ # JOIN product_product pp ON pp.id = sm.product_id
+ # JOIN product_template pt ON pt.id = pp.product_tmpl_id
+ # WHERE sp.state IN ('draft', 'waiting', 'confirmed', 'assigned')
+ # AND sp.name LIKE '%%OUT%%'
+ # AND sm.location_id = 55
+ # GROUP BY pp.id, pp.default_code, pt.default_code, pt.name, pp.brand_id
+ # ) a
+ # LEFT JOIN brands b ON b.id = a.brand_id
+ # LEFT JOIN LATERAL (
+ # SELECT vendor_id
+ # FROM purchase_pricelist
+ # WHERE product_id = a.product_id
+ # ORDER BY id ASC
+ # LIMIT 1
+ # ) pp2 ON true
+ # ) final
+ # WHERE final.action = 'kurang'
+ # """ % self._table)
+
+ # super(PurchasingJob, self).init()
+
+class PurchasingJobNote(models.Model):
+ _name = 'purchasing.job.note'
+ _description = 'Note for Product in Purchasing Job'
+
+ product_id = fields.Many2one('product.product', string='Product', required=True, ondelete='cascade', index=True)
+ note = fields.Text(string='Note')
+ user_id = fields.Many2one('res.users', string='Created by', default=lambda self: self.env.user)
+
+
+
+class PurchasingJobNoteWizard(models.TransientModel):
+ _name = 'purchasing.job.note.wizard'
+ _description = 'Add/Edit Note for Product'
+
+ product_id = fields.Many2one('product.product', string='Product', required=True)
+ note = fields.Text(string='Note')
+
+ def action_confirm(self):
+ self.ensure_one()
+ existing = self.env['purchasing.job.note'].search([
+ ('product_id', '=', self.product_id.id)
+ ], limit=1)
+ if existing:
+ existing.note = self.note
+ else:
+ self.env['purchasing.job.note'].create({
+ 'product_id': self.product_id.id,
+ 'note': self.note,
+ })
+ return {'type': 'ir.actions.act_window_close'}
diff --git a/fixco_custom/models/reordering_rule.py b/fixco_custom/models/reordering_rule.py
new file mode 100644
index 0000000..dad07b0
--- /dev/null
+++ b/fixco_custom/models/reordering_rule.py
@@ -0,0 +1,94 @@
+from odoo import models, fields, api
+from odoo import tools
+from odoo.exceptions import UserError
+
+class ReorderingRule(models.Model):
+ _name = 'reordering.rule'
+ _description = 'Buffer Stock'
+ _auto = False # Karena ini dari SQL view
+
+ product_id = fields.Many2one('product.product', string='Product', readonly=True)
+ min_stock = fields.Float(string='Min Stock', readonly=True)
+ buffer_stock = fields.Float(string='Buffer Stock', readonly=True)
+ vendor_id = fields.Many2one('res.partner', string='Vendor', readonly=True)
+ qty_onhand = fields.Float(string='Qty Onhand', readonly=True)
+ qty_incoming = fields.Float(string='Qty Incoming')
+
+ def create_automatic_purchase(self):
+ if not self:
+ raise UserError("No stock records selected.")
+
+ automatic_purchase = self.env['automatic.purchase'].create({
+ 'apo_type': 'reordering',
+ })
+
+ lines_to_create = []
+
+ for stock in self:
+ location_id = 55
+
+ quant_records = self.env['stock.quant'].search([
+ ('product_id', '=', stock.product_id.id),
+ ('location_id', '=', location_id)
+ ])
+
+ total_available = quant_records.quantity or 0.0
+
+ qty_incoming = stock.product_id.incoming_qty or 0.0
+
+ qty_purchase = stock.buffer_stock - (total_available + qty_incoming)
+
+ qty_purchase = max(qty_purchase, 0.0)
+
+ pricelist = self.env['purchase.pricelist'].search([
+ ('product_id', '=', stock.product_id.id),
+ ('vendor_id', '=', stock.vendor_id.id)
+ ], limit=1)
+
+ price = pricelist.price if pricelist else 0.0
+ subtotal = qty_purchase * price
+
+ lines_to_create.append({
+ 'automatic_purchase_id': automatic_purchase.id,
+ 'product_id': stock.product_id.id,
+ 'qty_purchase': qty_purchase,
+ 'qty_min': stock.min_stock,
+ 'qty_buffer': stock.buffer_stock,
+ 'partner_id': stock.vendor_id.id,
+ 'taxes_id': stock.vendor_id.tax_id.id if stock.vendor_id.tax_id else False,
+ 'price': price,
+ 'product_public_category_id': stock.product_id.product_public_category_id.id,
+ 'brand_id': stock.product_id.brand_id.id,
+ })
+
+ self.env['automatic.purchase.line'].create(lines_to_create)
+
+ return {
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'automatic.purchase',
+ 'view_mode': 'form',
+ 'res_id': automatic_purchase.id,
+ 'target': 'current',
+ }
+
+ def init(self):
+ tools.drop_view_if_exists(self.env.cr, self._table)
+ self.env.cr.execute(f"""
+ CREATE OR REPLACE VIEW {self._table} AS (
+ SELECT
+ vmsbm.id AS id,
+ vmsbm.product_id,
+ vmsbm.min_stock,
+ vmsbm.buffer_stock,
+ vmsbm.vendor_id,
+ COALESCE(SUM(sq.quantity), 0.0) AS qty_onhand,
+ vmsbm.incoming_qty AS qty_incoming
+ FROM
+ view_manage_stock_below_min vmsbm
+ LEFT JOIN
+ stock_quant sq ON sq.product_id = vmsbm.product_id AND sq.location_id = 55
+
+ GROUP BY
+ vmsbm.id, vmsbm.product_id, vmsbm.min_stock, vmsbm.buffer_stock, vmsbm.vendor_id, vmsbm.incoming_qty
+ )
+ """)