725 lines
25 KiB
Python
725 lines
25 KiB
Python
from odoo import http
|
|
from odoo.http import request
|
|
|
|
class AUIDashboardController(http.Controller):
|
|
|
|
@http.route("/aui/dashboard/overview", type="json", auth="user")
|
|
def get_overview_data(self):
|
|
env = request.env
|
|
|
|
# -------------------------------------------------
|
|
# BASIC COUNTS
|
|
# -------------------------------------------------
|
|
sales_count = env["sale.order"].search_count([])
|
|
purchase_count = env["purchase.order"].search_count([])
|
|
inventory_count = env["stock.quant"].search_count([])
|
|
work_orders_count = env["mrp.production"].search_count([])
|
|
landed_cost_count = env["stock.landed.cost"].search_count([
|
|
("state", "=", "done")
|
|
])
|
|
|
|
# -------------------------------------------------
|
|
# SALES & PURCHASE AMOUNTS
|
|
# -------------------------------------------------
|
|
sales_orders = env["sale.order"].search([
|
|
("state", "in", ["sale", "done"])
|
|
])
|
|
sales_amount = sum(sales_orders.mapped("amount_total"))
|
|
|
|
purchase_orders = env["purchase.order"].search([
|
|
("state", "in", ["purchase", "done"])
|
|
])
|
|
purchase_amount = sum(purchase_orders.mapped("amount_total"))
|
|
|
|
# -------------------------------------------------
|
|
# LANDED COST TOTAL
|
|
# -------------------------------------------------
|
|
landed_costs = env["stock.landed.cost"].search([
|
|
("state", "=", "done")
|
|
])
|
|
additional_exp_amount = sum(landed_costs.mapped("amount_total"))
|
|
|
|
# -------------------------------------------------
|
|
# LANDED COST LINES → DONUT DATA
|
|
# -------------------------------------------------
|
|
expense_map = {}
|
|
|
|
landed_cost_lines = env["stock.landed.cost.lines"].search([
|
|
("cost_id.state", "=", "done")
|
|
])
|
|
|
|
for line in landed_cost_lines:
|
|
product_name = line.product_id.display_name if line.product_id else "Other"
|
|
expense_map[product_name] = (
|
|
expense_map.get(product_name, 0.0)
|
|
+ (line.price_unit or 0.0)
|
|
)
|
|
|
|
additional_expenses = {
|
|
"labels": list(expense_map.keys()),
|
|
"series": list(expense_map.values()),
|
|
"total": sum(expense_map.values()),
|
|
"count": len(landed_cost_lines),
|
|
}
|
|
|
|
# -------------------------------------------------
|
|
# COST OF GOODS SOLD (RAW MATERIAL COST)
|
|
# -------------------------------------------------
|
|
# stock_moves = env["stock.move"].search([
|
|
# ("state", "=", "done"),
|
|
# ("sale_line_id", "!=", False),
|
|
# ("product_id.valuation", "!=", "manual_periodic"),
|
|
# ])
|
|
|
|
# stock.move.value is NEGATIVE for outgoing moves
|
|
# raw_material_cost = abs(sum(stock_moves.mapped("value")))
|
|
|
|
# -------------------------------------------------
|
|
# MANUFACTURING COST (OPTIONAL / CUSTOM)
|
|
# -------------------------------------------------
|
|
manufacturing_cost = 0.0
|
|
productions = env["mrp.production"].search([
|
|
("state", "=", "done")
|
|
])
|
|
|
|
# If you have custom cost field
|
|
if "extra_cost" in productions._fields:
|
|
manufacturing_cost = sum(productions.mapped("extra_cost"))
|
|
|
|
# -------------------------------------------------
|
|
# PROFIT CALCULATION
|
|
# -------------------------------------------------
|
|
total_cost = (
|
|
manufacturing_cost
|
|
+ additional_exp_amount
|
|
)
|
|
|
|
profit = sales_amount - total_cost
|
|
|
|
# -------------------------------------------------
|
|
# FINAL RESPONSE
|
|
# -------------------------------------------------
|
|
data = {
|
|
"sales": sales_count,
|
|
"purchase": purchase_count,
|
|
"inventory": inventory_count,
|
|
"workOrders": work_orders_count,
|
|
"landedCostCount": landed_cost_count,
|
|
|
|
"sales_amount": sales_amount,
|
|
"purchase_amount": purchase_amount,
|
|
"additional_exp_amount": additional_exp_amount,
|
|
"manufacturing_cost": manufacturing_cost,
|
|
"total_cost": total_cost,
|
|
"profit": profit,
|
|
|
|
"additional_expenses": additional_expenses,
|
|
}
|
|
|
|
return data
|
|
|
|
@http.route("/aui/dashboard/purchase_vendor_product_stacked", type="json", auth="user")
|
|
def get_purchase_vendor_product_stacked(self):
|
|
env = request.env
|
|
|
|
po_lines = env["purchase.order.line"].search([
|
|
("order_id.state", "in", ["purchase", "done"]),
|
|
])
|
|
|
|
# Structure:
|
|
# vendor_map = {
|
|
# "Vendor A": {"Product 1": 1000, "Product 2": 2000},
|
|
# "Vendor B": {"Product 1": 1500}
|
|
# }
|
|
vendor_map = {}
|
|
products_set = set()
|
|
|
|
for line in po_lines:
|
|
vendor = line.order_id.partner_id.name
|
|
product = line.product_id.display_name
|
|
amount = line.price_subtotal
|
|
|
|
products_set.add(product)
|
|
|
|
vendor_map.setdefault(vendor, {})
|
|
vendor_map[vendor][product] = (
|
|
vendor_map[vendor].get(product, 0.0) + amount
|
|
)
|
|
|
|
vendors = list(vendor_map.keys())
|
|
products = list(products_set)
|
|
|
|
# Build stacked series
|
|
series = []
|
|
for product in products:
|
|
series.append({
|
|
"name": product,
|
|
"data": [
|
|
vendor_map[vendor].get(product, 0.0)
|
|
for vendor in vendors
|
|
],
|
|
})
|
|
|
|
return {
|
|
"categories": vendors,
|
|
"series": series,
|
|
}
|
|
|
|
@http.route("/aui/dashboard/work_order_status", type="json", auth="user")
|
|
def get_work_order_status(self):
|
|
env = request.env
|
|
|
|
data = env["work.order"].read_group(
|
|
[],
|
|
["id:count"],
|
|
["state"]
|
|
)
|
|
|
|
labels = []
|
|
series = []
|
|
|
|
for row in data:
|
|
labels.append(dict(
|
|
env["work.order"]._fields["state"].selection
|
|
).get(row["state"]))
|
|
series.append(row["id"])
|
|
|
|
return {
|
|
"labels": labels,
|
|
"series": series,
|
|
}
|
|
|
|
@http.route("/aui/dashboard/finished_goods_output", type="json", auth="user")
|
|
def get_finished_goods_output(self):
|
|
env = request.env
|
|
|
|
data = env["work.order.inward"].read_group(
|
|
[("state", "=", "confirmed")],
|
|
["quantity:sum"],
|
|
["product_id"]
|
|
)
|
|
|
|
categories = []
|
|
series = [{
|
|
"name": "Produced Quantity",
|
|
"data": []
|
|
}]
|
|
|
|
for row in data:
|
|
categories.append(row["product_id"][1])
|
|
series[0]["data"].append(row["quantity"])
|
|
|
|
print({"categories": categories, "series": series})
|
|
|
|
return {
|
|
"categories": categories,
|
|
"series": series,
|
|
}
|
|
|
|
@http.route("/aui/dashboard/raw_material_consumption", type="json", auth="user")
|
|
def get_raw_material_consumption(self):
|
|
env = request.env
|
|
|
|
data = env["work.order.inward.raw.line"].read_group(
|
|
[],
|
|
["quantity:sum"],
|
|
["product_id"]
|
|
)
|
|
|
|
labels = []
|
|
series = []
|
|
|
|
for row in data:
|
|
labels.append(row["product_id"][1])
|
|
series.append(row["quantity"])
|
|
|
|
return {
|
|
"labels": labels,
|
|
"series": series,
|
|
}
|
|
|
|
@http.route("/aui/dashboard/work_order_material_flow", type="json", auth="user")
|
|
def get_work_order_material_flow(self):
|
|
env = request.env
|
|
|
|
# Limit to latest 6 work orders (best for demo)
|
|
work_orders = env["work.order"].search(
|
|
[("state", "in", ["partial", "done"])],
|
|
order="create_date desc",
|
|
limit=6
|
|
)
|
|
|
|
categories = []
|
|
sent_qty = []
|
|
used_qty = []
|
|
scrap_qty = []
|
|
finished_qty = []
|
|
|
|
for wo in work_orders:
|
|
categories.append(wo.name)
|
|
|
|
# Raw material SENT (KG)
|
|
sent = sum(wo.line_ids.mapped("quantity"))
|
|
|
|
# Raw material USED (KG)
|
|
used = sum(
|
|
env["work.order.inward.raw.line"]
|
|
.search([("inward_id.work_order_ids", "in", wo.id)])
|
|
.mapped("quantity")
|
|
)
|
|
|
|
# Scrap / loss (KG)
|
|
scrap = sum(
|
|
env["work.order.inward"]
|
|
.search([("work_order_ids", "in", wo.id)])
|
|
.mapped("difference")
|
|
)
|
|
|
|
# Finished goods (Units)
|
|
finished = sum(
|
|
env["work.order.inward"]
|
|
.search([("work_order_ids", "in", wo.id)])
|
|
.mapped("quantity")
|
|
)
|
|
|
|
sent_qty.append(sent)
|
|
used_qty.append(used)
|
|
scrap_qty.append(abs(scrap))
|
|
finished_qty.append(finished)
|
|
|
|
print({
|
|
"categories": categories,
|
|
"series": [
|
|
{
|
|
"name": "Sent (KG)",
|
|
"type": "bar",
|
|
"data": sent_qty,
|
|
},
|
|
{
|
|
"name": "Used (KG)",
|
|
"type": "bar",
|
|
"data": used_qty,
|
|
},
|
|
{
|
|
"name": "Scrap (KG)",
|
|
"type": "bar",
|
|
"data": scrap_qty,
|
|
},
|
|
{
|
|
"name": "Finished Units",
|
|
"type": "line",
|
|
"data": finished_qty,
|
|
},
|
|
],
|
|
})
|
|
|
|
return {
|
|
"categories": categories,
|
|
"series": [
|
|
{
|
|
"name": "Sent (KG)",
|
|
"type": "bar",
|
|
"data": sent_qty,
|
|
},
|
|
{
|
|
"name": "Used (KG)",
|
|
"type": "bar",
|
|
"data": used_qty,
|
|
},
|
|
{
|
|
"name": "Scrap (KG)",
|
|
"type": "bar",
|
|
"data": scrap_qty,
|
|
},
|
|
{
|
|
"name": "Finished Units",
|
|
"type": "line",
|
|
"data": finished_qty,
|
|
},
|
|
],
|
|
}
|
|
|
|
# Add this to your dashboard controller
|
|
@http.route('/aui/dashboard/work_order_analysis', type='json', auth='user')
|
|
def get_work_order_analysis(self):
|
|
# Get work orders with inward data
|
|
work_orders = request.env['work.order'].search([
|
|
('state', 'not in', ['draft', 'confirmed'])
|
|
], order='create_date desc', limit=20)
|
|
|
|
# Group by subcontractor
|
|
subcontractor_data = {}
|
|
|
|
for wo in work_orders:
|
|
subcontractor = wo.partner_id.name or 'Unknown'
|
|
|
|
if subcontractor not in subcontractor_data:
|
|
subcontractor_data[subcontractor] = {
|
|
'work_orders': [],
|
|
'total_raw_used': 0,
|
|
'total_fg_weight': 0,
|
|
'total_fg_units': 0,
|
|
'total_scrap': 0
|
|
}
|
|
|
|
# Get inward records for this work order
|
|
inward_ids = request.env['work.order.inward'].search([
|
|
('work_order_ids', 'in', [wo.id]),
|
|
('state', 'not in', ['draft'])
|
|
])
|
|
|
|
# Calculate totals
|
|
total_raw_sent = sum(wo.line_ids.mapped('quantity'))
|
|
total_raw_used = 0
|
|
total_fg_weight = 0
|
|
total_fg_units = 0
|
|
total_scrap = 0
|
|
|
|
inward_details = []
|
|
for inward in inward_ids:
|
|
# Raw material used
|
|
inward_raw_used = sum(inward.raw_line_ids.mapped('quantity'))
|
|
total_raw_used += inward_raw_used
|
|
|
|
# Finished goods
|
|
if inward.fg_weight:
|
|
total_fg_weight += inward.fg_weight
|
|
total_fg_units += inward.quantity
|
|
|
|
# Scrap
|
|
total_scrap += inward.difference or 0
|
|
|
|
# Store inward details for tooltip
|
|
inward_details.append({
|
|
'inward_name': inward.in_picking_id.display_name or f"Inward-{inward.id}",
|
|
'raw_material_used': inward_raw_used,
|
|
'fg_weight': inward.fg_weight or 0,
|
|
'fg_units': inward.quantity,
|
|
'scrap': inward.difference or 0,
|
|
'product_name': inward.product_id.display_name if inward.product_id else 'N/A'
|
|
})
|
|
|
|
wo_data = {
|
|
'work_order': wo.name,
|
|
'raw_material_sent': total_raw_sent,
|
|
'raw_material_used': total_raw_used,
|
|
'finished_goods_weight': total_fg_weight,
|
|
'finished_goods_units': total_fg_units,
|
|
'scrap': total_scrap,
|
|
'yield_percentage': (total_fg_weight / total_raw_used * 100) if total_raw_used > 0 else 0,
|
|
'status': wo.state,
|
|
'subcontractor': subcontractor,
|
|
'inward_details': inward_details
|
|
}
|
|
|
|
subcontractor_data[subcontractor]['work_orders'].append(wo_data)
|
|
|
|
# Update subcontractor totals
|
|
subcontractor_data[subcontractor]['total_raw_used'] += total_raw_used
|
|
subcontractor_data[subcontractor]['total_fg_weight'] += total_fg_weight
|
|
subcontractor_data[subcontractor]['total_fg_units'] += total_fg_units
|
|
subcontractor_data[subcontractor]['total_scrap'] += total_scrap
|
|
|
|
# Prepare series data for chart
|
|
series = [
|
|
{'name': 'Raw Material Used (kg)', 'data': []},
|
|
{'name': 'Finished Goods Weight (kg)', 'data': []},
|
|
{'name': 'Scrap/Difference (kg)', 'data': []}
|
|
]
|
|
|
|
categories = []
|
|
detailed_data = []
|
|
|
|
# Flatten data for chart - Group by subcontractor with work orders
|
|
for subcontractor, data in subcontractor_data.items():
|
|
for wo_data in data['work_orders']:
|
|
# Create category name: "Vendor - WO001"
|
|
category_name = f"{subcontractor} - {wo_data['work_order']}"
|
|
categories.append(category_name)
|
|
|
|
# Add data to series
|
|
series[0]['data'].append(wo_data['raw_material_used'])
|
|
series[1]['data'].append(wo_data['finished_goods_weight'])
|
|
series[2]['data'].append(wo_data['scrap'])
|
|
|
|
# Store detailed data for tooltips
|
|
detailed_data.append({
|
|
'category': category_name,
|
|
'work_order': wo_data['work_order'],
|
|
'subcontractor': subcontractor,
|
|
'raw_material_used': wo_data['raw_material_used'],
|
|
'finished_goods_weight': wo_data['finished_goods_weight'],
|
|
'finished_goods_units': wo_data['finished_goods_units'],
|
|
'scrap': wo_data['scrap'],
|
|
'yield_percentage': wo_data['yield_percentage'],
|
|
'status': wo_data['status'],
|
|
'inward_details': wo_data['inward_details']
|
|
})
|
|
|
|
return {
|
|
'series': series,
|
|
'categories': categories,
|
|
'detailed_data': detailed_data,
|
|
'subcontractor_summary': [
|
|
{
|
|
'name': k,
|
|
'total_raw_used': v['total_raw_used'],
|
|
'total_fg_weight': v['total_fg_weight'],
|
|
'total_fg_units': v['total_fg_units'],
|
|
'total_scrap': v['total_scrap'],
|
|
'work_order_count': len(v['work_orders'])
|
|
}
|
|
for k, v in subcontractor_data.items()
|
|
]
|
|
}
|
|
|
|
@http.route('/aui/dashboard/inventory_flow', type='json', auth='user')
|
|
def get_inventory_flow_data(self, category_ids=None, location_ids=None):
|
|
env = request.env
|
|
|
|
# Build domain for stock moves
|
|
domain = [('state', '=', 'done'), ('product_qty', '>', 0)]
|
|
|
|
if category_ids:
|
|
# Convert string IDs to integers if they come as strings
|
|
if isinstance(category_ids, str):
|
|
category_ids = [int(cat_id) for cat_id in category_ids.split(',')]
|
|
elif isinstance(category_ids, list):
|
|
category_ids = [int(cat_id) for cat_id in category_ids]
|
|
|
|
domain.append(('product_id.categ_id', 'in', category_ids))
|
|
|
|
if location_ids:
|
|
# Convert string IDs to integers
|
|
if isinstance(location_ids, str):
|
|
location_ids = [int(loc_id) for loc_id in location_ids.split(',')]
|
|
elif isinstance(location_ids, list):
|
|
location_ids = [int(loc_id) for loc_id in location_ids]
|
|
|
|
domain.append('|')
|
|
domain.append(('location_id', 'in', location_ids))
|
|
domain.append(('location_dest_id', 'in', location_ids))
|
|
|
|
# Get recent stock moves (limit to 50 for performance)
|
|
moves = env['stock.move'].search(domain, limit=50, order='date desc')
|
|
|
|
# Create nodes (unique locations)
|
|
nodes_set = set()
|
|
links = []
|
|
|
|
for move in moves:
|
|
if move.product_uom_qty <= 0:
|
|
continue
|
|
|
|
source = move.location_id.complete_name or 'Unknown Source'
|
|
target = move.location_dest_id.complete_name or 'Unknown Destination'
|
|
|
|
# Add to nodes set
|
|
nodes_set.add(source)
|
|
nodes_set.add(target)
|
|
|
|
# Create link
|
|
links.append({
|
|
'source': source,
|
|
'target': target,
|
|
'value': float(move.product_uom_qty),
|
|
'product': move.product_id.display_name if move.product_id else 'Unknown Product',
|
|
'move_name': move.reference or move.name or 'Move',
|
|
'date': move.date.strftime('%Y-%m-%d') if move.date else '',
|
|
'uom': move.product_uom.name if move.product_uom else 'units'
|
|
})
|
|
|
|
# Convert nodes set to list
|
|
nodes = [{'name': node} for node in nodes_set]
|
|
|
|
# Get available categories for filter
|
|
categories = env['product.category'].search_read([], ['id', 'name', 'complete_name'])
|
|
|
|
# Get available locations for filter
|
|
locations = env['stock.location'].search_read([
|
|
('usage', 'in', ['internal', 'transit']),
|
|
('company_id', '=', env.company.id)
|
|
], ['id', 'complete_name'])
|
|
|
|
return {
|
|
'nodes': nodes,
|
|
'links': links,
|
|
'filters': {
|
|
'categories': categories,
|
|
'locations': locations
|
|
},
|
|
'total_moves': len(links),
|
|
'total_quantity': sum(link['value'] for link in links) if links else 0
|
|
}
|
|
|
|
@http.route('/aui/dashboard/inventory_heatmap', type='json', auth='user')
|
|
def get_inventory_heatmap_data(self, category_ids=None, location_ids=None):
|
|
env = request.env
|
|
|
|
domain = [
|
|
('location_id.usage', '=', 'internal'),
|
|
('quantity', '>', 0),
|
|
('company_id', '=', env.company.id),
|
|
]
|
|
|
|
if location_ids:
|
|
location_ids = [int(x) for x in location_ids.split(',')] if isinstance(location_ids, str) else location_ids
|
|
domain.append(('location_id', 'in', location_ids))
|
|
|
|
if category_ids:
|
|
category_ids = [int(x) for x in category_ids.split(',')] if isinstance(category_ids, str) else category_ids
|
|
domain.append(('product_id.categ_id', 'in', category_ids))
|
|
|
|
quants = env['stock.quant'].search(domain)
|
|
|
|
# location → category → data
|
|
matrix = {}
|
|
all_locations = set()
|
|
all_categories = set()
|
|
|
|
for q in quants:
|
|
location = q.location_id.complete_name
|
|
category = q.product_id.categ_id.name or 'Uncategorized'
|
|
|
|
location = location.replace('WH/Stock', 'Main Warehouse')
|
|
location = location.replace('WO_', 'Work Order ')
|
|
location = location.replace('SO_', 'Sales Order ')
|
|
|
|
all_locations.add(location)
|
|
all_categories.add(category)
|
|
|
|
matrix.setdefault(location, {})
|
|
matrix[location].setdefault(category, {
|
|
'quantity': 0.0,
|
|
'products': set(),
|
|
})
|
|
|
|
matrix[location][category]['quantity'] += q.quantity
|
|
matrix[location][category]['products'].add(q.product_id.id)
|
|
|
|
# Find max qty for normalization
|
|
max_qty = max(
|
|
(cell['quantity'] for loc in matrix.values() for cell in loc.values()),
|
|
default=1
|
|
)
|
|
|
|
heatmap_data = []
|
|
|
|
for location in all_locations:
|
|
for category in all_categories:
|
|
cell = matrix.get(location, {}).get(category, {'quantity': 0, 'products': set()})
|
|
qty = cell['quantity']
|
|
|
|
heatmap_data.append({
|
|
'location': location,
|
|
'category': category,
|
|
'value': qty,
|
|
'product_count': len(cell['products']),
|
|
'intensity': round((qty / max_qty) * 100, 2),
|
|
})
|
|
|
|
return {
|
|
'locations': list(all_locations) if all_locations else [],
|
|
'categories': list(all_categories) if all_categories else [],
|
|
'data': heatmap_data if heatmap_data else [],
|
|
'total_quantity': sum(d['value'] for d in heatmap_data) if heatmap_data else 0,
|
|
}
|
|
|
|
# @http.route('/aui/dashboard/inventory_heatmap', type='json', auth='user')
|
|
# def get_inventory_heatmap_data(self, category_ids=None, location_ids=None):
|
|
# env = request.env
|
|
#
|
|
# domain = [
|
|
# ('location_id.usage', '=', 'internal'),
|
|
# ('quantity', '>', 0),
|
|
# ('company_id', '=', env.company.id)
|
|
# ]
|
|
#
|
|
# if location_ids:
|
|
# if isinstance(location_ids, str):
|
|
# location_ids = [int(loc_id) for loc_id in location_ids.split(',')]
|
|
# elif isinstance(location_ids, list):
|
|
# location_ids = [int(loc_id) for loc_id in location_ids]
|
|
# domain.append(('location_id', 'in', location_ids))
|
|
#
|
|
# if category_ids:
|
|
# if isinstance(category_ids, str):
|
|
# category_ids = [int(cat_id) for cat_id in category_ids.split(',')]
|
|
# elif isinstance(category_ids, list):
|
|
# category_ids = [int(cat_id) for cat_id in category_ids]
|
|
# domain.append(('product_id.categ_id', 'in', category_ids))
|
|
#
|
|
# # Get all stock quants
|
|
# quants = env['stock.quant'].search(domain)
|
|
#
|
|
# print(f"Found {len(quants)} stock quants")
|
|
#
|
|
# # Aggregate manually for better control
|
|
# aggregated_data = {}
|
|
#
|
|
# for quant in quants:
|
|
# if not quant.product_id or not quant.location_id:
|
|
# continue
|
|
#
|
|
# location_name = quant.location_id.complete_name
|
|
# category_name = quant.product_id.categ_id.name if quant.product_id.categ_id else 'Uncategorized'
|
|
#
|
|
# # Use full location path but clean it up
|
|
# location_name = location_name.replace('WH/Stock', 'Main Warehouse')
|
|
# location_name = location_name.replace('WO_', 'Work Order ')
|
|
# location_name = location_name.replace('SO_', 'Sales Order ')
|
|
#
|
|
# # Create a unique key for location-category combination
|
|
# key = f"{location_name}|{category_name}"
|
|
#
|
|
# if key in aggregated_data:
|
|
# aggregated_data[key]['value'] += quant.quantity
|
|
# aggregated_data[key]['product_count'] += 1
|
|
# else:
|
|
# aggregated_data[key] = {
|
|
# 'location': location_name,
|
|
# 'category': category_name,
|
|
# 'value': quant.quantity,
|
|
# 'product_count': 1
|
|
# }
|
|
#
|
|
# # Convert to list
|
|
# heatmap_data = list(aggregated_data.values())
|
|
#
|
|
# # Get unique locations and categories
|
|
# unique_locations = sorted(set(item['location'] for item in heatmap_data))
|
|
# unique_categories = sorted(set(item['category'] for item in heatmap_data))
|
|
#
|
|
# # Get filter options
|
|
# category_list = env['product.category'].search_read([], ['id', 'name'])
|
|
# location_list = env['stock.location'].search_read([
|
|
# ('usage', '=', 'internal'),
|
|
# ('company_id', '=', env.company.id)
|
|
# ], ['id', 'complete_name'])
|
|
# print({
|
|
# 'locations': unique_locations,
|
|
# 'categories': unique_categories,
|
|
# 'data': heatmap_data,
|
|
# 'total_quantity': sum(item['value'] for item in heatmap_data),
|
|
# 'filters': {
|
|
# 'categories': category_list,
|
|
# 'locations': location_list
|
|
# }
|
|
# })
|
|
# result = {
|
|
# 'locations': unique_locations,
|
|
# 'categories': unique_categories,
|
|
# 'data': heatmap_data,
|
|
# 'total_quantity': sum(item['value'] for item in heatmap_data),
|
|
# 'filters': {
|
|
# 'categories': category_list,
|
|
# 'locations': location_list
|
|
# }
|
|
# }
|
|
#
|
|
# print(f"Returning {len(heatmap_data)} data points")
|
|
# print(f"Unique locations: {unique_locations}")
|
|
# print(f"Unique categories: {unique_categories}")
|
|
#
|
|
# return result
|