mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-22 21:51:59 +02:00
19.0 vanilla
This commit is contained in:
parent
d1963a3c3a
commit
2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions
|
|
@ -1,9 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, tools
|
||||
from odoo import api, fields, models, tools
|
||||
from odoo.tools import formatLang
|
||||
|
||||
|
||||
class PurchaseBillUnion(models.Model):
|
||||
_name = 'purchase.bill.union'
|
||||
_auto = False
|
||||
|
|
@ -30,19 +31,20 @@ class PurchaseBillUnion(models.Model):
|
|||
id as vendor_bill_id, NULL as purchase_order_id
|
||||
FROM account_move
|
||||
WHERE
|
||||
move_type='in_invoice' and state = 'posted'
|
||||
move_type in ('in_invoice', 'in_refund') and state = 'posted'
|
||||
UNION
|
||||
SELECT
|
||||
-id, name, partner_ref as reference, partner_id, date_order::date as date, amount_untaxed as amount, currency_id, company_id,
|
||||
NULL as vendor_bill_id, id as purchase_order_id
|
||||
FROM purchase_order
|
||||
WHERE
|
||||
state in ('purchase', 'done') AND
|
||||
state = 'purchase' AND
|
||||
invoice_status in ('to invoice', 'no')
|
||||
)""")
|
||||
|
||||
def name_get(self):
|
||||
result = []
|
||||
@api.depends('currency_id', 'reference', 'amount', 'purchase_order_id')
|
||||
@api.depends_context('show_total_amount')
|
||||
def _compute_display_name(self):
|
||||
for doc in self:
|
||||
name = doc.name or ''
|
||||
if doc.reference:
|
||||
|
|
@ -50,6 +52,5 @@ class PurchaseBillUnion(models.Model):
|
|||
amount = doc.amount
|
||||
if doc.purchase_order_id and doc.purchase_order_id.invoice_status == 'no':
|
||||
amount = 0.0
|
||||
name += ': ' + formatLang(self.env, amount, monetary=True, currency_obj=doc.currency_id)
|
||||
result.append((doc.id, name))
|
||||
return result
|
||||
name += ': ' + formatLang(self.env, amount, currency_obj=doc.currency_id)
|
||||
doc.display_name = name
|
||||
|
|
|
|||
|
|
@ -18,18 +18,18 @@
|
|||
</record>
|
||||
|
||||
<record id="view_purchase_bill_union_tree" model="ir.ui.view">
|
||||
<field name="name">purchase.bill.union.tree</field>
|
||||
<field name="name">purchase.bill.union.list</field>
|
||||
<field name="model">purchase.bill.union</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Reference Document">
|
||||
<list string="Reference Document">
|
||||
<field name="name"/>
|
||||
<field name="reference"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="date"/>
|
||||
<field name="amount"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="currency_id" column_invisible="True"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
|
||||
</tree>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
</t>
|
||||
<t t-if="o.dest_address_id">
|
||||
<t t-set="information_block">
|
||||
<strong>Shipping address:</strong>
|
||||
<strong class="d-block mt-3">Shipping address</strong>
|
||||
<div t-if="o.dest_address_id">
|
||||
<div t-field="o.dest_address_id"
|
||||
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True, "phone_icons": True}' name="purchase_shipping_address"/>
|
||||
|
|
@ -21,72 +21,82 @@
|
|||
<div class="oe_structure"/>
|
||||
|
||||
<div class="mt-4">
|
||||
<h2 t-if="o.state in ['draft', 'sent', 'to approve']">Request for Quotation #<span t-field="o.name"/></h2>
|
||||
<h2 t-if="o.state in ['purchase', 'done']">Purchase Order #<span t-field="o.name"/></h2>
|
||||
<h2 t-if="o.state == 'cancel'">Cancelled Purchase Order #<span t-field="o.name"/></h2>
|
||||
<t t-set="layout_document_title">
|
||||
<t t-if="o.state in ['draft', 'sent', 'to approve']">Request for Quotation #<span t-field="o.name"/></t>
|
||||
<t t-if="o.state == 'purchase'">Purchase Order #<span t-field="o.name"/></t>
|
||||
<t t-if="o.state == 'cancel'">Cancelled Purchase Order #<span t-field="o.name"/></t>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<div id="informations" class="row mt-4 mb32">
|
||||
<div t-if="o.user_id" class="col-3 bm-2">
|
||||
<strong>Purchase Representative:</strong>
|
||||
<p t-field="o.user_id" class="m-0"/>
|
||||
<div id="informations" class="row mb-4">
|
||||
<div t-if="o.user_id" class="col">
|
||||
<strong>Buyer</strong>
|
||||
<div t-field="o.user_id"/>
|
||||
</div>
|
||||
<div t-if="o.partner_ref" class="col-3 bm-2">
|
||||
<strong>Your Order Reference:</strong>
|
||||
<p t-field="o.partner_ref" class="m-0"/>
|
||||
<div t-if="o.partner_ref" class="col">
|
||||
<strong>Your Order Reference</strong>
|
||||
<div t-field="o.partner_ref"/>
|
||||
</div>
|
||||
<div t-if="o.state in ['purchase','done'] and o.date_approve" class="col-3 bm-2">
|
||||
<div t-if="o.state == 'purchase' and o.date_approve" class="col-3 bm-2">
|
||||
<strong>Order Date:</strong>
|
||||
<p t-field="o.date_approve" class="m-0"/>
|
||||
<p t-field="o.date_approve" t-options="{'date_only': 'true'}" class="m-0"/>
|
||||
</div>
|
||||
<div t-elif="o.date_order" class="col-3 bm-2">
|
||||
<strong >Order Deadline:</strong>
|
||||
<p t-field="o.date_order" class="m-0"/>
|
||||
<div t-elif="o.date_order" class="col-2 bm-2">
|
||||
<strong>Order Deadline:</strong>
|
||||
<p t-field="o.date_order" t-options="{'date_only': 'true'}" class="m-0"/>
|
||||
</div>
|
||||
<div t-if="o.date_planned" class="col-2 bm-2">
|
||||
<strong>Expected Arrival:</strong>
|
||||
<p t-field="o.date_planned" t-options="{'date_only': 'true'}" class="m-0"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-sm o_main_table table-borderless mt-4">
|
||||
<table class="o_has_total_table table o_main_table table-borderless">
|
||||
<thead style="display: table-row-group">
|
||||
<tr>
|
||||
<th name="th_description"><strong>Description</strong></th>
|
||||
<th name="th_taxes"><strong>Taxes</strong></th>
|
||||
<th name="th_date_req" class="text-center"><strong>Date Req.</strong></th>
|
||||
<th name="th_description" class="text-start"><strong>Description</strong></th>
|
||||
<th name="th_quantity" class="text-end"><strong>Qty</strong></th>
|
||||
<th name="th_price_unit" class="text-end"><strong>Unit Price</strong></th>
|
||||
<th name="th_amount" class="text-end"><strong>Amount</strong></th>
|
||||
<th name="th_discount" class="text-end"><strong>Disc.</strong></th>
|
||||
<th name="th_taxes" class="text-end"><strong>Taxes</strong></th>
|
||||
<th name="th_subtotal" class="text-end">
|
||||
<strong>Amount</strong>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
<t t-foreach="o.order_line" t-as="line">
|
||||
<t t-set="current_subtotal" t-value="current_subtotal + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<t t-set="current_subtotal" t-value="current_subtotal + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
<t t-foreach="o.order_line.filtered(lambda l: l.display_type or l.product_qty != 0)" t-as="line">
|
||||
<t t-set="current_subtotal" t-value="current_subtotal + line.price_subtotal"/>
|
||||
|
||||
<tr t-att-class="'bg-200 fw-bold o_line_section' if line.display_type == 'line_section' else 'fst-italic o_line_note' if line.display_type == 'line_note' else ''">
|
||||
<tr t-att-class="'fw-bolder o_line_section' if line.display_type == 'line_section' else 'fw-bold o_line_subsection' if line.display_type == 'line_subsection' else 'fst-italic o_line_note' if line.display_type == 'line_note' else ''">
|
||||
<t t-if="not line.display_type">
|
||||
<td id="product">
|
||||
<td id="product" class="text-start">
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
<t t-set="taxes" t-value="', '.join([(tax.description or tax.name) for tax in line.taxes_id])"/>
|
||||
<td name="td_taxes" t-attf-class="text-end {{ 'text-nowrap' if len(taxes) < 10 else '' }}">
|
||||
<span t-out="taxes">Tax 15%</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span t-field="line.date_planned"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-field="line.product_qty"/>
|
||||
<span t-field="line.product_uom.name" groups="uom.group_uom"/>
|
||||
<span t-field="line.product_uom_id.name" groups="uom.group_uom"/>
|
||||
<span t-if="line.product_uom_id != line.product_id.uom_id" class="text-muted small">
|
||||
<br/>
|
||||
<span t-field="line.product_uom_qty" t-options="{'widget': 'float', 'decimal_precision': 'Product Unit'}"/> <span t-field="line.product_id.uom_id"/>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-field="line.price_unit"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span class="text-align-bottom"><span t-field="line.discount"/>%</span>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-out="', '.join(tax.tax_label for tax in line.tax_ids if tax.tax_label)"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-field="line.price_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
</t>
|
||||
<t t-if="line.display_type == 'line_section'">
|
||||
<t t-if="line.display_type in ('line_section', 'line_subsection')">
|
||||
<td colspan="99" id="section">
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
|
|
@ -99,12 +109,12 @@
|
|||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
<t t-if="current_section and (line_last or o.order_line[line_index+1].display_type == 'line_section')">
|
||||
<t t-if="current_section and (line_last or o.order_line[line_index+1].display_type in ('line_section', 'line_subsection'))">
|
||||
<tr class="is-subtotal text-end">
|
||||
<td colspan="99" id="subtotal">
|
||||
<strong class="mr16">Subtotal</strong>
|
||||
<span
|
||||
t-esc="current_subtotal"
|
||||
t-out="current_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'
|
||||
/>
|
||||
</td>
|
||||
|
|
@ -114,17 +124,28 @@
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<div id="total" class="row justify-content-end">
|
||||
<div id="total" class="row justify-content-end mt-n3">
|
||||
<div class="col-4">
|
||||
<table class="table table-sm table-borderless">
|
||||
<t t-set="tax_totals" t-value="o.tax_totals"/>
|
||||
<t t-call="account.document_tax_totals"/>
|
||||
<table class="o_total_table table table-borderless">
|
||||
<t t-call="purchase.document_tax_totals">
|
||||
<t t-set="tax_totals" t-value="o.tax_totals"/>
|
||||
<t t-set="currency" t-value="o.currency_id"/>
|
||||
</t>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p t-field="o.notes" class="mt-4"/>
|
||||
<p t-field="o.note" class="mt-4"/>
|
||||
<div class="oe_structure"/>
|
||||
|
||||
<strong>Payment Terms: </strong>
|
||||
<span t-field="o.payment_term_id" class="mt-4"></span>
|
||||
|
||||
<t t-set="base_address" t-value="o.env['ir.config_parameter'].sudo().get_param('web.base.url')"/>
|
||||
<t t-set="portal_url" t-value="base_address + '/my/purchase/' + str(o.id) + '#portal_connect_software_modal'"/>
|
||||
<div t-if="any(u._is_portal() for u in o.partner_id.user_ids) and o._get_edi_builders()" class="text-center">
|
||||
<a t-att-href="portal_url">Connect your software</a> with <t t-out="o.company_id.name"/> to create quotes automatically.
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
|
@ -136,4 +157,8 @@
|
|||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Allow edits (e.g. studio) without changing the often inherited base template -->
|
||||
<template id="document_tax_totals" inherit_id="account.document_tax_totals_template" primary="True"></template>
|
||||
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -10,17 +10,22 @@
|
|||
</t>
|
||||
<t t-if="o.dest_address_id">
|
||||
<t t-set="information_block">
|
||||
<strong>Shipping address:</strong>
|
||||
<strong class="d-block mt-3">Shipping address</strong>
|
||||
<div t-field="o.dest_address_id"
|
||||
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True, "phone_icons": True}' name="purchase_shipping_address"/>
|
||||
<div>
|
||||
<strong>Requested Ship Date:</strong>
|
||||
<span t-field="o.date_planned" t-options="{'date_only': 'true'}"/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
<div class="page">
|
||||
<div class="oe_structure"/>
|
||||
<t t-set="layout_document_title">
|
||||
<span>Request for Quotation <span t-field="o.name"/></span>
|
||||
</t>
|
||||
|
||||
<h2 class="mt-4">Request for Quotation <span t-field="o.name"/></h2>
|
||||
|
||||
<table class="table table-sm mt-4">
|
||||
<table class="table table-borderless">
|
||||
<thead style="display: table-row-group">
|
||||
<tr>
|
||||
<th name="th_description"><strong>Description</strong></th>
|
||||
|
|
@ -29,18 +34,22 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="o.order_line" t-as="order_line">
|
||||
<tr t-att-class="'bg-200 fw-bold o_line_section' if order_line.display_type == 'line_section' else 'fst-italic o_line_note' if order_line.display_type == 'line_note' else ''">
|
||||
<t t-foreach="o.order_line.filtered(lambda l: l.display_type or l.product_qty != 0)" t-as="order_line">
|
||||
<tr t-att-class="'fw-bolder o_line_section' if order_line.display_type == 'line_section' else 'fw-bold o_line_subsection' if order_line.display_type == 'line_subsection' else 'fst-italic o_line_note' if order_line.display_type == 'line_note' else ''">
|
||||
<t t-if="not order_line.display_type">
|
||||
<td id="product">
|
||||
<span t-field="order_line.name"/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span t-field="order_line.date_planned"/>
|
||||
<span t-field="order_line.date_planned" t-options="{'date_only': 'true'}"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-field="order_line.product_qty"/>
|
||||
<span t-field="order_line.product_uom" groups="uom.group_uom"/>
|
||||
<span t-field="order_line.product_uom_id" groups="uom.group_uom"/>
|
||||
<span t-if="order_line.product_uom_id != order_line.product_id.uom_id" class="text-muted small">
|
||||
<br/>
|
||||
<span t-field="order_line.product_uom_qty" t-options="{'widget': 'float', 'decimal_precision': 'Product Unit'}"/> <span t-field="order_line.product_id.uom_id"/>
|
||||
</span>
|
||||
</td>
|
||||
</t>
|
||||
<t t-else="">
|
||||
|
|
@ -53,9 +62,15 @@
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<p t-field="o.notes" class="mt-4"/>
|
||||
<p t-field="o.note" class="mt-4"/>
|
||||
|
||||
<div class="oe_structure"/>
|
||||
|
||||
<t t-set="base_address" t-value="o.env['ir.config_parameter'].sudo().get_param('web.base.url')"/>
|
||||
<t t-set="portal_url" t-value="base_address + '/my/purchase/' + str(o.id) + '#portal_connect_software_modal'"/>
|
||||
<div t-if="any(u._is_portal() for u in o.partner_id.user_ids) and o._get_edi_builders()" class="text-center">
|
||||
<a t-att-href="portal_url">Connect your software</a> with <t t-out="o.company_id.name"/> to create quotes automatically.
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,15 +5,13 @@
|
|||
# Please note that these reports are not multi-currency !!!
|
||||
#
|
||||
|
||||
import re
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.osv.expression import AND, expression
|
||||
from odoo import fields, models, api
|
||||
from odoo.tools.query import Query
|
||||
from odoo.tools.sql import SQL
|
||||
|
||||
|
||||
class PurchaseReport(models.Model):
|
||||
_name = "purchase.report"
|
||||
_name = 'purchase.report'
|
||||
_description = "Purchase Report"
|
||||
_auto = False
|
||||
_order = 'date_order desc, price_total desc'
|
||||
|
|
@ -24,25 +22,20 @@ class PurchaseReport(models.Model):
|
|||
('sent', 'RFQ Sent'),
|
||||
('to approve', 'To Approve'),
|
||||
('purchase', 'Purchase Order'),
|
||||
('done', 'Done'),
|
||||
('cancel', 'Cancelled')
|
||||
], 'Status', readonly=True)
|
||||
product_id = fields.Many2one('product.product', 'Product', readonly=True)
|
||||
partner_id = fields.Many2one('res.partner', 'Vendor', readonly=True)
|
||||
date_approve = fields.Datetime('Confirmation Date', readonly=True)
|
||||
product_uom = fields.Many2one('uom.uom', 'Reference Unit of Measure', required=True)
|
||||
product_uom_id = fields.Many2one('uom.uom', 'Reference Unit of Measure', readonly=True)
|
||||
company_id = fields.Many2one('res.company', 'Company', readonly=True)
|
||||
currency_id = fields.Many2one('res.currency', 'Currency', readonly=True)
|
||||
user_id = fields.Many2one('res.users', 'Purchase Representative', readonly=True)
|
||||
delay = fields.Float('Days to Confirm', digits=(16, 2), readonly=True, group_operator='avg', help="Amount of time between purchase approval and order by date.")
|
||||
delay_pass = fields.Float('Days to Receive', digits=(16, 2), readonly=True, group_operator='avg',
|
||||
user_id = fields.Many2one('res.users', 'Buyer', readonly=True)
|
||||
delay = fields.Float('Days to Confirm', digits=(16, 2), readonly=True, aggregator='avg', help="Amount of time between purchase approval and order by date.")
|
||||
delay_pass = fields.Float('Days to Receive', digits=(16, 2), readonly=True, aggregator='avg',
|
||||
help="Amount of time between date planned and order by date for each purchase order line.")
|
||||
avg_days_to_purchase = fields.Float(
|
||||
'Average Days to Purchase', digits=(16, 2), readonly=True, store=False, # needs store=False to prevent showing up as a 'measure' option
|
||||
help="Amount of time between purchase approval and document creation date. Due to a hack needed to calculate this, \
|
||||
every record will show the same average value, therefore only use this as an aggregated value with group_operator=avg")
|
||||
price_total = fields.Float('Total', readonly=True)
|
||||
price_average = fields.Float('Average Cost', readonly=True, group_operator="avg", digits='Product Price')
|
||||
price_total = fields.Monetary('Total', readonly=True)
|
||||
price_average = fields.Monetary('Average Cost', readonly=True, aggregator="avg")
|
||||
nbr_lines = fields.Integer('# of Lines', readonly=True)
|
||||
category_id = fields.Many2one('product.category', 'Product Category', readonly=True)
|
||||
product_tmpl_id = fields.Many2one('product.template', 'Product Template', readonly=True)
|
||||
|
|
@ -52,19 +45,20 @@ class PurchaseReport(models.Model):
|
|||
weight = fields.Float('Gross Weight', readonly=True)
|
||||
volume = fields.Float('Volume', readonly=True)
|
||||
order_id = fields.Many2one('purchase.order', 'Order', readonly=True)
|
||||
untaxed_total = fields.Float('Untaxed Total', readonly=True)
|
||||
untaxed_total = fields.Monetary('Untaxed Total', readonly=True)
|
||||
qty_ordered = fields.Float('Qty Ordered', readonly=True)
|
||||
qty_received = fields.Float('Qty Received', readonly=True)
|
||||
qty_billed = fields.Float('Qty Billed', readonly=True)
|
||||
qty_to_be_billed = fields.Float('Qty to be Billed', readonly=True)
|
||||
|
||||
@property
|
||||
def _table_query(self):
|
||||
def _table_query(self) -> SQL:
|
||||
''' Report needs to be dynamic to take into account multi-company selected + multi-currency rates '''
|
||||
return '%s %s %s %s' % (self._select(), self._from(), self._where(), self._group_by())
|
||||
return SQL("%s %s %s %s", self._select(), self._from(), self._where(), self._group_by())
|
||||
|
||||
def _select(self):
|
||||
select_str = """
|
||||
def _select(self) -> SQL:
|
||||
return SQL(
|
||||
"""
|
||||
SELECT
|
||||
po.id as order_id,
|
||||
min(l.id) as id,
|
||||
|
|
@ -80,29 +74,30 @@ class PurchaseReport(models.Model):
|
|||
p.product_tmpl_id,
|
||||
t.categ_id as category_id,
|
||||
c.currency_id,
|
||||
t.uom_id as product_uom,
|
||||
t.uom_id as product_uom_id,
|
||||
extract(epoch from age(po.date_approve,po.date_order))/(24*60*60)::decimal(16,2) as delay,
|
||||
extract(epoch from age(l.date_planned,po.date_order))/(24*60*60)::decimal(16,2) as delay_pass,
|
||||
count(*) as nbr_lines,
|
||||
sum(l.price_total / COALESCE(po.currency_rate, 1.0))::decimal(16,2) * currency_table.rate as price_total,
|
||||
(sum(l.product_qty * l.price_unit / COALESCE(po.currency_rate, 1.0))/NULLIF(sum(l.product_qty/line_uom.factor*product_uom.factor),0.0))::decimal(16,2) * currency_table.rate as price_average,
|
||||
sum(l.price_total / COALESCE(po.currency_rate, 1.0))::decimal(16,2) * account_currency_table.rate as price_total,
|
||||
(sum(l.product_qty * l.price_unit / COALESCE(po.currency_rate, 1.0))/NULLIF(sum(l.product_qty * line_uom.factor / product_uom.factor),0.0))::decimal(16,2) * account_currency_table.rate as price_average,
|
||||
partner.country_id as country_id,
|
||||
partner.commercial_partner_id as commercial_partner_id,
|
||||
sum(p.weight * l.product_qty/line_uom.factor*product_uom.factor) as weight,
|
||||
sum(p.volume * l.product_qty/line_uom.factor*product_uom.factor) as volume,
|
||||
sum(l.price_subtotal / COALESCE(po.currency_rate, 1.0))::decimal(16,2) * currency_table.rate as untaxed_total,
|
||||
sum(l.product_qty / line_uom.factor * product_uom.factor) as qty_ordered,
|
||||
sum(l.qty_received / line_uom.factor * product_uom.factor) as qty_received,
|
||||
sum(l.qty_invoiced / line_uom.factor * product_uom.factor) as qty_billed,
|
||||
case when t.purchase_method = 'purchase'
|
||||
then sum(l.product_qty / line_uom.factor * product_uom.factor) - sum(l.qty_invoiced / line_uom.factor * product_uom.factor)
|
||||
else sum(l.qty_received / line_uom.factor * product_uom.factor) - sum(l.qty_invoiced / line_uom.factor * product_uom.factor)
|
||||
sum(p.weight * l.product_qty * line_uom.factor / product_uom.factor) as weight,
|
||||
sum(p.volume * l.product_qty * line_uom.factor / product_uom.factor) as volume,
|
||||
sum(l.price_subtotal / COALESCE(po.currency_rate, 1.0))::decimal(16,2) * account_currency_table.rate as untaxed_total,
|
||||
sum(l.product_qty * line_uom.factor / product_uom.factor) as qty_ordered,
|
||||
sum(l.qty_received * line_uom.factor / product_uom.factor) as qty_received,
|
||||
sum(l.qty_invoiced * line_uom.factor / product_uom.factor) as qty_billed,
|
||||
case when t.purchase_method = 'purchase'
|
||||
then sum(l.product_qty * line_uom.factor / product_uom.factor) - sum(l.qty_invoiced * line_uom.factor / product_uom.factor)
|
||||
else sum(l.qty_received * line_uom.factor / product_uom.factor) - sum(l.qty_invoiced * line_uom.factor / product_uom.factor)
|
||||
end as qty_to_be_billed
|
||||
"""
|
||||
return select_str
|
||||
""",
|
||||
)
|
||||
|
||||
def _from(self):
|
||||
from_str = """
|
||||
def _from(self) -> SQL:
|
||||
return SQL(
|
||||
"""
|
||||
FROM
|
||||
purchase_order_line l
|
||||
join purchase_order po on (l.order_id=po.id)
|
||||
|
|
@ -110,22 +105,24 @@ class PurchaseReport(models.Model):
|
|||
left join product_product p on (l.product_id=p.id)
|
||||
left join product_template t on (p.product_tmpl_id=t.id)
|
||||
left join res_company C ON C.id = po.company_id
|
||||
left join uom_uom line_uom on (line_uom.id=l.product_uom)
|
||||
left join uom_uom line_uom on (line_uom.id=l.product_uom_id)
|
||||
left join uom_uom product_uom on (product_uom.id=t.uom_id)
|
||||
left join {currency_table} ON currency_table.company_id = po.company_id
|
||||
""".format(
|
||||
currency_table=self.env['res.currency']._get_query_currency_table({'multi_company': True, 'date': {'date_to': fields.Date.today()}}),
|
||||
left join %(currency_table)s ON account_currency_table.company_id = po.company_id
|
||||
""",
|
||||
currency_table=self.env['res.currency']._get_simple_currency_table(self.env.companies),
|
||||
)
|
||||
return from_str
|
||||
|
||||
def _where(self):
|
||||
return """
|
||||
def _where(self) -> SQL:
|
||||
return SQL(
|
||||
"""
|
||||
WHERE
|
||||
l.display_type IS NULL
|
||||
"""
|
||||
""",
|
||||
)
|
||||
|
||||
def _group_by(self):
|
||||
group_by_str = """
|
||||
def _group_by(self) -> SQL:
|
||||
return SQL(
|
||||
"""
|
||||
GROUP BY
|
||||
po.company_id,
|
||||
po.user_id,
|
||||
|
|
@ -135,7 +132,7 @@ class PurchaseReport(models.Model):
|
|||
l.price_unit,
|
||||
po.date_approve,
|
||||
l.date_planned,
|
||||
l.product_uom,
|
||||
l.product_uom_id,
|
||||
po.dest_address_id,
|
||||
po.fiscal_position_id,
|
||||
l.product_id,
|
||||
|
|
@ -143,8 +140,6 @@ class PurchaseReport(models.Model):
|
|||
t.categ_id,
|
||||
po.date_order,
|
||||
po.state,
|
||||
line_uom.uom_type,
|
||||
line_uom.category_id,
|
||||
t.uom_id,
|
||||
t.purchase_method,
|
||||
line_uom.id,
|
||||
|
|
@ -152,63 +147,16 @@ class PurchaseReport(models.Model):
|
|||
partner.country_id,
|
||||
partner.commercial_partner_id,
|
||||
po.id,
|
||||
currency_table.rate
|
||||
"""
|
||||
return group_by_str
|
||||
account_currency_table.rate
|
||||
""",
|
||||
)
|
||||
|
||||
@api.model
|
||||
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
|
||||
""" This is a hack to allow us to correctly calculate the average of PO specific date values since
|
||||
the normal report query result will duplicate PO values across its PO lines during joins and
|
||||
lead to incorrect aggregation values.
|
||||
|
||||
Only the AVG operator is supported for avg_days_to_purchase.
|
||||
"""
|
||||
avg_days_to_purchase = next((field for field in fields if re.search(r'\bavg_days_to_purchase\b', field)), False)
|
||||
|
||||
if avg_days_to_purchase:
|
||||
fields.remove(avg_days_to_purchase)
|
||||
if any(field.split(':')[1].split('(')[0] != 'avg' for field in [avg_days_to_purchase] if field):
|
||||
raise UserError(_("Value: 'avg_days_to_purchase' should only be used to show an average. If you are seeing this message then it is being accessed incorrectly."))
|
||||
|
||||
if 'price_average:avg' in fields:
|
||||
fields.extend(['aggregated_qty_ordered:array_agg(qty_ordered)'])
|
||||
fields.extend(['aggregated_price_average:array_agg(price_average)'])
|
||||
|
||||
res = []
|
||||
if fields:
|
||||
res = super(PurchaseReport, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
|
||||
|
||||
if 'price_average:avg' in fields:
|
||||
qties = 'aggregated_qty_ordered'
|
||||
special_field = 'aggregated_price_average'
|
||||
for data in res:
|
||||
if data[special_field] and data[qties]:
|
||||
total_unit_cost = sum(float(value) * float(qty) for value, qty in zip(data[special_field], data[qties]) if qty and value)
|
||||
total_qty_ordered = sum(float(qty) for qty in data[qties] if qty)
|
||||
data['price_average'] = (total_unit_cost / total_qty_ordered) if total_qty_ordered else 0
|
||||
del data[special_field]
|
||||
del data[qties]
|
||||
if not res and avg_days_to_purchase:
|
||||
res = [{}]
|
||||
|
||||
if avg_days_to_purchase:
|
||||
self.check_access_rights('read')
|
||||
query = """ SELECT AVG(days_to_purchase.po_days_to_purchase)::decimal(16,2) AS avg_days_to_purchase
|
||||
FROM (
|
||||
SELECT extract(epoch from age(po.date_approve,po.create_date))/(24*60*60) AS po_days_to_purchase
|
||||
FROM purchase_order po
|
||||
WHERE po.id IN (
|
||||
SELECT "purchase_report"."order_id" FROM %s WHERE %s)
|
||||
) AS days_to_purchase
|
||||
"""
|
||||
|
||||
subdomain = AND([domain, [('company_id', '=', self.env.company.id), ('date_approve', '!=', False)]])
|
||||
subtables, subwhere, subparams = expression(subdomain, self).query.get_sql()
|
||||
|
||||
self.env.cr.execute(query % (subtables, subwhere), subparams)
|
||||
res[0].update({
|
||||
'__count': 1,
|
||||
avg_days_to_purchase.split(':')[0]: self.env.cr.fetchall()[0][0],
|
||||
})
|
||||
return res
|
||||
def _read_group_select(self, aggregate_spec: str, query: Query) -> SQL:
|
||||
""" This override allows us to correctly calculate the average price of products. """
|
||||
if aggregate_spec != 'price_average:avg':
|
||||
return super()._read_group_select(aggregate_spec, query)
|
||||
return SQL(
|
||||
'SUM(%(f_price)s * %(f_qty)s) / NULLIF(SUM(%(f_qty)s), 0.0)',
|
||||
f_qty=self._field_to_sql(self._table, 'qty_ordered', query),
|
||||
f_price=self._field_to_sql(self._table, 'price_average', query),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<field name="arch" type="xml">
|
||||
<pivot string="Purchase Analysis" display_quantity="1" sample="1">
|
||||
<field name="category_id" type="row"/>
|
||||
<field name="order_id" type="measure"/>
|
||||
<field name="order_id" type="row"/>
|
||||
<field name="untaxed_total" type="measure"/>
|
||||
<field name="price_total" type="measure"/>
|
||||
</pivot>
|
||||
|
|
@ -24,11 +24,11 @@
|
|||
</record>
|
||||
|
||||
<record id="purchase_report_view_tree" model="ir.ui.view">
|
||||
<field name="name">purchase.report.view.tree</field>
|
||||
<field name="name">purchase.report.view.list</field>
|
||||
<field name="model">purchase.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Purchase Analysis">
|
||||
<field name="date_order" widget="date"/>
|
||||
<list string="Purchase Analysis">
|
||||
<field name="date_order"/>
|
||||
<field name="order_id" optional="show"/>
|
||||
<field name="partner_id" optional="show"/>
|
||||
<field name="product_id" optional="show"/>
|
||||
|
|
@ -38,11 +38,11 @@
|
|||
<field name="qty_ordered" optional="hide" sum="Sum of Qty Ordered"/>
|
||||
<field name="qty_received" optional="hide" sum="Sum of Qty Received"/>
|
||||
<field name="qty_billed" optional="hide" sum="Sum of Qty Billed"/>
|
||||
<field name="currency_id" optional="show" invisible="1"/>
|
||||
<field name="currency_id" optional="show" column_invisible="True"/>
|
||||
<field name="untaxed_total" optional="hide" widget="monetary" sum="Sum of Untaxed Total"/>
|
||||
<field name="price_total" optional="show" widget="monetary" sum="Sum of Total"/>
|
||||
<field name="state" optional="show"/>
|
||||
</tree>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
|
@ -53,22 +53,22 @@
|
|||
<search string="Purchase Orders">
|
||||
<filter string="Requests for Quotation" name="quotes" domain="[('state','in',('draft','sent'))]"/>
|
||||
<filter string="Purchase Orders" name="orders" domain="[('state','!=','draft'), ('state','!=','sent'), ('state','!=','cancel')]"/>
|
||||
<filter string="Confirmation Date Last Year" name="later_than_a_year_ago" domain="[('date_approve', '>=', ((context_today()-relativedelta(years=1)).strftime('%Y-%m-%d')))]"/>
|
||||
<filter string="Confirmation Date Last Year" name="later_than_a_year_ago" domain="[('date_approve', '>=', 'today -1y')]"/>
|
||||
<filter name="filter_date_order" date="date_order"/>
|
||||
<filter name="filter_date_approve" date="date_approve" default_period="this_month"/>
|
||||
<filter name="filter_date_approve" date="date_approve" default_period="month"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="product_id"/>
|
||||
<group expand="0" string="Extended Filters">
|
||||
<group>
|
||||
<field name="user_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="date_order"/>
|
||||
<field name="date_approve"/>
|
||||
<field name="category_id" filter_domain="[('category_id', 'child_of', self)]"/>
|
||||
</group>
|
||||
<group expand="1" string="Group By">
|
||||
<group>
|
||||
<filter string="Vendor" name="group_partner_id" context="{'group_by':'partner_id'}"/>
|
||||
<filter string="Vendor Country" name="country_id" context="{'group_by':'country_id'}"/>
|
||||
<filter string="Purchase Representative" name="user_id" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Buyer" name="user_id" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Product" name="group_product_id" context="{'group_by':'product_id'}"/>
|
||||
<filter string="Product Category" name="group_category_id" context="{'group_by':'category_id'}"/>
|
||||
<filter string="Status" name="status" context="{'group_by':'state'}"/>
|
||||
|
|
@ -84,8 +84,12 @@
|
|||
<record id="action_purchase_order_report_all" model="ir.actions.act_window">
|
||||
<field name="name">Purchase Analysis</field>
|
||||
<field name="res_model">purchase.report</field>
|
||||
<field name="path">purchase-analysis</field>
|
||||
<field name="view_mode">graph,pivot</field>
|
||||
<field name="view_id"></field> <!-- force empty -->
|
||||
<field name="context">{
|
||||
'search_default_orders': 1,
|
||||
'search_default_filter_date_approve': 1}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No Purchase Analysis
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue