Initial commit: Sale packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:49 +02:00
commit 14e3d26998
6469 changed files with 2479670 additions and 0 deletions

View file

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import product_label_report
from . import product_pricelist_report

View file

@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from odoo import _, models
from odoo.exceptions import UserError
def _prepare_data(env, data):
# change product ids by actual product object to get access to fields in xml template
# we needed to pass ids because reports only accepts native python types (int, float, strings, ...)
if data.get('active_model') == 'product.template':
Product = env['product.template'].with_context(display_default_code=False)
elif data.get('active_model') == 'product.product':
Product = env['product.product'].with_context(display_default_code=False)
else:
raise UserError(_('Product model not defined, Please contact your administrator.'))
total = 0
qty_by_product_in = data.get('quantity_by_product')
# search for products all at once, ordered by name desc since popitem() used in xml to print the labels
# is LIFO, which results in ordering by product name in the report
products = Product.search([('id', 'in', [int(p) for p in qty_by_product_in.keys()])], order='name desc')
quantity_by_product = defaultdict(list)
for product in products:
q = qty_by_product_in[str(product.id)]
quantity_by_product[product].append((product.barcode, q))
total += q
if data.get('custom_barcodes'):
# we expect custom barcodes format as: {product: [(barcode, qty_of_barcode)]}
for product, barcodes_qtys in data.get('custom_barcodes').items():
quantity_by_product[Product.browse(int(product))] += (barcodes_qtys)
total += sum(qty for _, qty in barcodes_qtys)
layout_wizard = env['product.label.layout'].browse(data.get('layout_wizard'))
if not layout_wizard:
return {}
return {
'quantity': quantity_by_product,
'rows': layout_wizard.rows,
'columns': layout_wizard.columns,
'page_numbers': (total - 1) // (layout_wizard.rows * layout_wizard.columns) + 1,
'price_included': data.get('price_included'),
'extra_html': layout_wizard.extra_html,
}
class ReportProductTemplateLabel(models.AbstractModel):
_name = 'report.product.report_producttemplatelabel'
_description = 'Product Label Report'
def _get_report_values(self, docids, data):
return _prepare_data(self.env, data)
class ReportProductTemplateLabelDymo(models.AbstractModel):
_name = 'report.product.report_producttemplatelabel_dymo'
_description = 'Product Label Report'
def _get_report_values(self, docids, data):
return _prepare_data(self.env, data)

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<template id="report_packagingbarcode">
<t t-call="web.basic_layout">
<div class="page">
<t t-foreach="docs" t-as="packaging">
<div class="col-4" style="padding:0;">
<table class="table table-condensed" style="border-bottom: 0px solid white !important;width: 3in;">
<tr>
<th style="text-align: left;">
<strong t-field="packaging.name"/>
</th>
</tr>
<tr>
<td>
<strong t-field="packaging.product_id.display_name"/>
</td>
</tr>
<tr>
<td>
<div class="o_row">
<strong>Qty: </strong>
<strong t-field="packaging.qty"/>
<strong t-field="packaging.product_uom_id" groups="uom.group_uom"/>
</div>
</td>
</tr>
<t t-if="packaging.barcode">
<tr>
<td style="text-align: center; vertical-align: middle;" class="col-5">
<div t-field="packaging.barcode" t-options="{'widget': 'barcode', 'symbology': 'auto', 'width': 600, 'height': 150, 'img_style': 'width:100%;height:20%;'}"/>
<span t-field="packaging.barcode"/>
</td>
</tr>
</t>
</table>
</div>
</t>
</div>
</t>
</template>
</data>
</odoo>

View file

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
class ProductPricelistReport(models.AbstractModel):
_name = 'report.product.report_pricelist'
_description = 'Pricelist Report'
def _get_report_values(self, docids, data):
return self._get_report_data(data, 'pdf')
@api.model
def get_html(self, data):
render_values = self._get_report_data(data, 'html')
return self.env['ir.qweb']._render('product.report_pricelist_page', render_values)
def _get_report_data(self, data, report_type='html'):
quantities = data.get('quantities', [1])
data_pricelist_id = data.get('pricelist_id')
pricelist_id = data_pricelist_id and int(data_pricelist_id)
pricelist = self.env['product.pricelist'].browse(pricelist_id).exists()
if not pricelist:
pricelist = self.env['product.pricelist'].search([], limit=1)
active_model = data.get('active_model', 'product.template')
active_ids = data.get('active_ids') or []
is_product_tmpl = active_model == 'product.template'
ProductClass = self.env[active_model]
products = ProductClass.browse(active_ids) if active_ids else ProductClass.search([('sale_ok', '=', True)])
products_data = [
self._get_product_data(is_product_tmpl, product, pricelist, quantities)
for product in products
]
return {
'is_html_type': report_type == 'html',
'is_product_tmpl': is_product_tmpl,
'is_visible_title': data.get('is_visible_title', False) and bool(data['is_visible_title']),
'pricelist': pricelist,
'products': products_data,
'quantities': quantities,
}
def _get_product_data(self, is_product_tmpl, product, pricelist, quantities):
data = {
'id': product.id,
'name': is_product_tmpl and product.name or product.display_name,
'price': dict.fromkeys(quantities, 0.0),
'uom': product.uom_id.name,
}
for qty in quantities:
data['price'][qty] = pricelist._get_product_price(product, qty)
if is_product_tmpl and product.product_variant_count > 1:
data['variants'] = [
self._get_product_data(False, variant, pricelist, quantities)
for variant in product.product_variant_ids
]
return data

View file

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_pricelist_page">
<div class="container bg-white p-4 my-4">
<div class="row my-3">
<div class="col-12" t-if="is_visible_title">
<h2 t-if="is_html_type">
Pricelist:
<a href="#" class="o_action" data-model="product.pricelist" t-att-data-res-id="pricelist.id">
<t t-esc="pricelist.display_name"/>
</a>
</h2>
<h2 t-else="">
Pricelist: <t t-esc="pricelist.display_name"/>
</h2>
</div>
</div>
<div class="row">
<div t-att-class="'text-center' + (' offset-4' if is_html_type else ' offset-3')">
<strong>Sales Order Line Quantities (price per unit)</strong>
</div>
</div>
<div class="row">
<div class="col-12">
<table class="table table-sm">
<thead>
<tr>
<th>Products</th>
<th groups="uom.group_uom">UoM</th>
<t t-foreach="quantities" t-as="qty">
<th class="text-end"><t t-esc="qty"/></th>
</t>
</tr>
</thead>
<tbody>
<t t-foreach="products" t-as="product">
<tr>
<td t-att-class="is_product_tmpl and 'fw-bold' or None">
<a t-if="is_html_type" href="#" class="o_action" t-att-data-model="is_product_tmpl and 'product.template' or 'product.product'" t-att-data-res-id="product['id']">
<t t-esc="product['name']"/>
</a>
<t t-else="">
<t t-esc="product['name']"/>
</t>
</td>
<td groups="uom.group_uom">
<t t-esc="product['uom']"/>
</td>
<t t-foreach="quantities" t-as="qty">
<td class="text-end">
<t t-esc="product['price'][qty]" t-options='{"widget": "monetary", "display_currency": pricelist.currency_id}'/>
</td>
</t>
</tr>
<t t-if="is_product_tmpl and 'variants' in product">
<tr t-foreach="product['variants']" t-as="variant">
<td>
<a t-if="is_html_type" href="#" class="o_action ms-4" data-model="product.product" t-att-data-res-id="variant['id']">
<t t-esc="variant['name']"/>
</a>
<span t-else="" class="ms-4" t-esc="variant['name']"/>
</td>
<td groups="uom.group_uom">
<t t-esc="product['uom']"/>
</td>
<t t-foreach="quantities" t-as="qty">
<td class="text-end">
<t t-esc="variant['price'][qty]" t-options='{"widget": "monetary", "display_currency": pricelist.currency_id}'/>
</td>
</t>
</tr>
</t>
</t>
</tbody>
</table>
</div>
</div>
</div>
</template>
<template id="report_pricelist">
<t t-call="web.basic_layout">
<div class="page">
<t t-call="product.report_pricelist_page"/>
</div>
<p style="page-break-before:always;"> </p>
</t>
</template>
</odoo>

View file

@ -0,0 +1,203 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<template id="report_simple_label2x7">
<t t-set="barcode_size" t-value="'width:33mm;height:14mm'"/>
<t t-set="table_style" t-value="'width:97mm;height:37.1mm;' + table_style"/>
<td t-att-style="make_invisible and 'visibility:hidden;'" >
<div class="o_label_full" t-att-style="table_style">
<div class="o_label_name">
<strong t-field="product.display_name"/>
</div>
<div class="o_label_data">
<div class="text-center o_label_left_column">
<span class="text-nowrap" t-field="product.default_code"/>
<t t-if="barcode">
<div t-out="barcode" t-options="{'widget': 'barcode', 'symbology': 'auto', 'img_style': barcode_size}"/>
<span class="text-center" t-out="barcode"/>
</t>
</div>
<div class="text-end" style="line-height:normal">
<div class="o_label_extra_data">
<t t-out="extra_html"/>
</div>
<t t-if="product.is_product_variant">
<strong class="o_label_price" t-field="product.lst_price" t-options="{'widget': 'monetary', 'label_price': True}"/>
</t>
<t t-else="">
<strong class="o_label_price" t-field="product.list_price" t-options="{'widget': 'monetary', 'label_price': True}"/>
</t>
</div>
<div class="o_label_clear"></div>
</div>
</div>
</td>
</template>
<template id="report_simple_label4x7">
<t t-set="barcode_size" t-value="'width:33mm;height:8mm'"/>
<t t-set="table_style" t-value="'width:47mm;height:37.1mm;' + table_style"/>
<td t-att-style="make_invisible and 'visibility:hidden;'" >
<div class="o_label_full" t-att-style="table_style">
<div class="o_label_name">
<strong t-field="product.display_name"/>
</div>
<div class="text-end" style="padding-top:0;padding-bottom:0">
<t t-if="product.is_product_variant">
<strong class="o_label_price_medium" t-field="product.lst_price" t-options="{'widget': 'monetary', 'label_price': True}"/>
</t>
<t t-else="">
<strong class="o_label_price_medium" t-field="product.list_price" t-options="{'widget': 'monetary', 'label_price': True}"/>
</t>
</div>
<div class= "text-center o_label_small_barcode">
<span class="text-nowrap" t-field="product.default_code"/>
<t t-if="barcode">
<div t-out="barcode" style="padding:0" t-options="{'widget': 'barcode', 'symbology': 'auto', 'img_style': barcode_size}"/>
<span class="text-center" t-out="barcode"/>
</t>
</div>
</div>
</td>
</template>
<template id="report_simple_label4x12">
<t t-set="barcode_size" t-value="'width:33mm;height:4mm'"/>
<t t-set="table_style" t-value="'width:43mm;height:19mm;' + table_style"/>
<td t-att-style="make_invisible and 'visibility:hidden;'" >
<div class="o_label_full o_label_small_text" t-att-style="table_style">
<div class="o_label_name">
<strong t-field="product.display_name"/>
</div>
<t t-if="price_included">
<div class="o_label_left_column">
<span class="text-nowrap" t-field="product.default_code"/>
</div>
<div class="o_label_price_medium text-end">
<t t-if="product.is_product_variant">
<strong t-field="product.lst_price" t-options="{'widget': 'monetary', 'label_price': True}"/>
</t>
<t t-else="">
<strong t-field="product.list_price" t-options="{'widget': 'monetary', 'label_price': True}"/>
</t>
</div>
</t>
<t t-else="">
<div class="o_label_left_column o_label_full_with">
<span class="text-nowrap" t-field="product.default_code"/>
</div>
</t>
<div class= "text-center o_label_small_barcode">
<t t-if="barcode">
<div t-out="barcode" style="padding:0" t-options="{'widget': 'barcode', 'symbology': 'auto', 'img_style': barcode_size}"/>
<span class="text-center" t-out="barcode"/>
</t>
</div>
</div>
</td>
</template>
<template id="report_simple_label_dymo">
<div class="o_label_sheet o_label_dymo" t-att-style="padding_page">
<div class="o_label_full" t-att-style="table_style">
<div class= "text-start o_label_small_barcode">
<t t-if="barcode">
<!-- `quiet=0` to remove the left and right margins on the barcode -->
<div t-out="barcode" style="padding:0" t-options="{'widget': 'barcode', 'quiet': 0, 'symbology': 'auto', 'img_style': barcode_size}"/>
<div class="o_label_name" style="height:1.7em;background-color: transparent;">
<span t-out="barcode"/>
</div>
</t>
</div>
<div class="o_label_name" style="line-height: 100%;background-color: transparent;padding-top: 1px;">
<span t-if="product.is_product_variant" t-field="product.display_name"/>
<span t-else="" t-field="product.name"/>
</div>
<div class="o_label_left_column">
<small class="text-nowrap" t-field="product.default_code"/>
</div>
<div class="text-end" style="padding: 0 4px;">
<t t-if="product.is_product_variant">
<strong class="o_label_price_small" t-field="product.lst_price" t-options="{'widget': 'monetary', 'label_price': True}"/>
</t>
<t t-else="">
<strong class="o_label_price_small" t-field="product.list_price" t-options="{'widget': 'monetary', 'label_price': True}"/>
</t>
<div t-if="False" class="o_label_extra_data">
<t t-out="extra_html"/>
</div>
</div>
</div>
</div>
</template>
<template id="report_productlabel">
<t t-call="web.html_container">
<t t-if="columns and rows">
<t t-if="columns == 2 and rows == 7">
<t t-set="padding_page" t-value="'padding: 14mm 3mm'"/>
<t t-set="report_to_call" t-value="'product.report_simple_label2x7'"/>
</t>
<t t-if="columns == 4 and rows == 7">
<t t-set="padding_page" t-value="'padding: 14mm 3mm'"/>
<t t-set="report_to_call" t-value="'product.report_simple_label4x7'"/>
</t>
<t t-if="columns == 4 and rows == 12">
<t t-set="padding_page" t-value="'padding: 20mm 8mm'"/>
<t t-set="report_to_call" t-value="'product.report_simple_label4x12'"/>
</t>
<t t-foreach="range(page_numbers)" t-as="page">
<div class="o_label_sheet" t-att-style="padding_page">
<table class="my-0 table table-sm table-borderless">
<t t-foreach="range(rows)" t-as="row">
<tr>
<t t-foreach="range(columns)" t-as="column">
<t t-if="not current_quantity and quantity and not (current_data and current_data[1])">
<t t-set="current_data" t-value="quantity.popitem()"/>
<t t-set="product" t-value="current_data[0]"/>
<t t-set="barcode_and_qty" t-value="current_data[1].pop()"/>
<t t-set="barcode" t-value="barcode_and_qty[0]"/>
<t t-set="current_quantity" t-value="barcode_and_qty[1]"/>
</t>
<t t-if="current_quantity">
<t t-set="make_invisible" t-value="False"/>
<t t-set="current_quantity" t-value="current_quantity - 1"/>
</t>
<t t-elif="current_data and current_data[1]">
<t t-set="barcode_and_qty" t-value="current_data[1].pop()"/>
<t t-set="barcode" t-value="barcode_and_qty[0]"/>
<t t-set="current_quantity" t-value="barcode_and_qty[1] - 1"/>
</t>
<t t-else="">
<t t-set="make_invisible" t-value="True"/>
</t>
<t t-set="table_style" t-value="'border: 1px solid %s;' % (product.env.user.company_id.primary_color or 'black')"/>
<t t-call="{{report_to_call}}"/>
</t>
</tr>
</t>
</table>
</div>
</t>
</t>
</t>
</template>
<template id="report_productlabel_dymo">
<t t-call="web.html_container">
<t t-set="barcode_size" t-value="'width:45.5mm;height:7.5mm'"/>
<t t-set="table_style" t-value="'width:100%;height:32mm;'"/>
<t t-set="padding_page" t-value="'padding: 2mm'"/>
<t t-foreach="quantity.items()" t-as="barcode_and_qty_by_product">
<t t-set="product" t-value="barcode_and_qty_by_product[0]"/>
<t t-foreach="barcode_and_qty_by_product[1]" t-as="barcode_and_qty">
<t t-set="barcode" t-value="barcode_and_qty[0]"/>
<t t-foreach="range(barcode_and_qty[1])" t-as="qty">
<t t-call="product.report_simple_label_dymo"/>
</t>
</t>
</t>
</t>
</template>
</data>
</odoo>

View file

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="paperformat_label_sheet" model="report.paperformat">
<field name="name">A4 Label Sheet</field>
<field name="default" eval="True" />
<field name="format">A4</field>
<field name="page_height">0</field>
<field name="page_width">0</field>
<field name="orientation">Portrait</field>
<field name="margin_top">0</field>
<field name="margin_bottom">0</field>
<field name="margin_left">0</field>
<field name="margin_right">0</field>
<field name="disable_shrinking" eval="True"/>
<field name="dpi">96</field>
</record>
<record id="report_product_template_label" model="ir.actions.report">
<field name="name">Product Label (PDF)</field>
<field name="model">product.template</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">product.report_producttemplatelabel</field>
<field name="report_file">product.report_producttemplatelabel</field>
<field name="paperformat_id" ref="product.paperformat_label_sheet"/>
<field name="print_report_name">'Products Labels - %s' % (object.name)</field>
<field name="binding_model_id" eval="False"/>
<field name="binding_type">report</field>
</record>
<record id="report_product_packaging" model="ir.actions.report">
<field name="name">Product Packaging (PDF)</field>
<field name="model">product.packaging</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">product.report_packagingbarcode</field>
<field name="report_file">product.report_packagingbarcode</field>
<field name="print_report_name">'Products packaging - %s' % (object.name)</field>
<field name="binding_model_id" ref="product.model_product_packaging"/>
<field name="binding_type">report</field>
</record>
<record id="action_report_pricelist" model="ir.actions.report">
<field name="name">Pricelist</field>
<field name="model">product.product</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">product.report_pricelist</field>
<field name="report_file">product.report_pricelist</field>
</record>
<record id="paperformat_label_sheet_dymo" model="report.paperformat">
<field name="name">Dymo Label Sheet</field>
<field name="default" eval="True" />
<field name="format">custom</field>
<field name="page_height">57</field>
<field name="page_width">32</field>
<field name="orientation">Landscape</field>
<field name="margin_top">0</field>
<field name="margin_bottom">0</field>
<field name="margin_left">0</field>
<field name="margin_right">0</field>
<field name="disable_shrinking" eval="True"/>
<field name="dpi">96</field>
</record>
<record id="report_product_template_label_dymo" model="ir.actions.report">
<field name="name">Product Label (PDF)</field>
<field name="model">product.template</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">product.report_producttemplatelabel_dymo</field>
<field name="report_file">product.report_producttemplatelabel_dymo</field>
<field name="paperformat_id" ref="product.paperformat_label_sheet_dymo"/>
<field name="print_report_name">'Products Labels - %s' % (object.name)</field>
<field name="binding_type">report</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_producttemplatelabel">
<t t-call="web.basic_layout">
<div class="page">
<t t-call="product.report_productlabel">
<t t-set="products" t-value="products"/>
</t>
</div>
</t>
</template>
<template id="report_producttemplatelabel_dymo">
<t t-call="web.basic_layout">
<div class="page">
<t t-call="product.report_productlabel_dymo">
<t t-set="products" t-value="products"/>
</t>
</div>
</t>
</template>
</odoo>