first commit

This commit is contained in:
root
2026-03-14 09:42:12 +00:00
commit 0adbd20c2c
10991 changed files with 1646955 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,93 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.model import (
ModelSingleton, ModelSQL, ModelView, ValueMixin, fields)
from trytond.modules.company.model import (
CompanyMultiValueMixin, CompanyValueMixin)
from trytond.pool import Pool
from trytond.pyson import Eval, Id, TimeDelta
purchase_invoice_method = fields.Selection(
'get_purchase_invoice_method', "Invoice Method")
def get_purchase_methods(field_name):
@classmethod
def func(cls):
pool = Pool()
Purchase = pool.get('purchase.purchase')
return Purchase.fields_get([field_name])[field_name]['selection']
return func
class Configuration(
ModelSingleton, ModelSQL, ModelView, CompanyMultiValueMixin):
__name__ = 'purchase.configuration'
purchase_sequence = fields.MultiValue(fields.Many2One(
'ir.sequence', "Purchase Sequence", required=True,
domain=[
('company', 'in',
[Eval('context', {}).get('company', -1), None]),
('sequence_type', '=',
Id('purchase', 'sequence_type_purchase')),
]))
purchase_invoice_method = fields.MultiValue(purchase_invoice_method)
get_purchase_invoice_method = get_purchase_methods('invoice_method')
purchase_process_after = fields.TimeDelta(
"Process Purchase after",
domain=['OR',
('purchase_process_after', '=', None),
('purchase_process_after', '>=', TimeDelta()),
],
help="The grace period during which confirmed purchase "
"can still be reset to draft.\n"
"Applied only if a worker queue is activated.")
@classmethod
def multivalue_model(cls, field):
pool = Pool()
if field == 'purchase_invoice_method':
return pool.get('purchase.configuration.purchase_method')
if field == 'purchase_sequence':
return pool.get('purchase.configuration.sequence')
return super().multivalue_model(field)
@classmethod
def default_purchase_sequence(cls, **pattern):
return cls.multivalue_model(
'purchase_sequence').default_purchase_sequence()
@classmethod
def default_purchase_invoice_method(cls, **pattern):
return cls.multivalue_model(
'purchase_invoice_method').default_purchase_invoice_method()
class ConfigurationSequence(ModelSQL, CompanyValueMixin):
__name__ = 'purchase.configuration.sequence'
purchase_sequence = fields.Many2One(
'ir.sequence', "Purchase Sequence", required=True,
domain=[
('company', 'in', [Eval('company', -1), None]),
('sequence_type', '=',
Id('purchase', 'sequence_type_purchase')),
])
@classmethod
def default_purchase_sequence(cls):
pool = Pool()
ModelData = pool.get('ir.model.data')
try:
return ModelData.get_id('purchase', 'sequence_purchase')
except KeyError:
return None
class ConfigurationPurchaseMethod(ModelSQL, ValueMixin):
__name__ = 'purchase.configuration.purchase_method'
purchase_invoice_method = purchase_invoice_method
get_purchase_invoice_method = get_purchase_methods('invoice_method')
@classmethod
def default_purchase_invoice_method(cls):
return 'order'

View File

@@ -0,0 +1,57 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<!-- set active for 1.4 migration -->
<menuitem
name="Configuration"
parent="menu_purchase"
sequence="0"
id="menu_configuration"
icon="tryton-settings"
active="1"/>
<record model="ir.ui.menu-res.group" id="menu_configuration_group_purchase_admin">
<field name="menu" ref="menu_configuration"/>
<field name="group" ref="group_purchase_admin"/>
</record>
<record model="ir.ui.view" id="purchase_configuration_view_form">
<field name="model">purchase.configuration</field>
<field name="type">form</field>
<field name="name">configuration_form</field>
</record>
<record model="ir.action.act_window" id="act_purchase_configuration_form">
<field name="name">Configuration</field>
<field name="res_model">purchase.configuration</field>
</record>
<record model="ir.action.act_window.view"
id="act_purchase_configuration_view1">
<field name="sequence" eval="1"/>
<field name="view" ref="purchase_configuration_view_form"/>
<field name="act_window" ref="act_purchase_configuration_form"/>
</record>
<menuitem
parent="menu_configuration"
action="act_purchase_configuration_form"
sequence="10"
id="menu_purchase_configuration"
icon="tryton-list"/>
<record model="ir.model.access" id="access_purchase_configuration">
<field name="model">purchase.configuration</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_purchase_configuration_purchase_admin">
<field name="model">purchase.configuration</field>
<field name="group" ref="group_purchase_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,21 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.exceptions import UserError, UserWarning
from trytond.model.exceptions import ValidationError
class PurchaseUOMWarning(UserWarning):
pass
class PurchaseQuotationError(ValidationError):
pass
class PurchaseMoveQuantity(UserWarning):
pass
class PartyLocationError(UserError):
pass

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zM1 2v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.14 0-.25-.11-.25-.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.58-6.49c.08-.14.12-.31.12-.48 0-.55-.45-1-1-1H5.21l-.94-2H1zm16 16c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 471 B

177
modules/purchase/invoice.py Normal file
View File

@@ -0,0 +1,177 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from functools import wraps
from trytond.i18n import gettext
from trytond.model import ModelView, Workflow, fields
from trytond.model.exceptions import AccessError
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.tools import cached_property
from trytond.transaction import Transaction, without_check_access
def process_purchase(func):
@wraps(func)
def wrapper(cls, invoices):
pool = Pool()
Purchase = pool.get('purchase.purchase')
transaction = Transaction()
context = transaction.context
with without_check_access():
purchases = set(
p for i in cls.browse(invoices) for p in i.purchases)
result = func(cls, invoices)
if purchases:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Purchase.__queue__.process(purchases)
return result
return wrapper
class Invoice(metaclass=PoolMeta):
__name__ = 'account.invoice'
purchase_exception_state = fields.Function(fields.Selection([
('', ''),
('ignored', 'Ignored'),
('recreated', 'Recreated'),
], 'Exception State'), 'get_purchase_exception_state')
purchases = fields.Function(fields.Many2Many(
'purchase.purchase', None, None, "Purchases"),
'get_purchases', searcher='search_purchases')
def get_purchase_exception_state(self, name):
purchases = self.purchases
recreated = tuple(i for p in purchases for i in p.invoices_recreated)
ignored = tuple(i for p in purchases for i in p.invoices_ignored)
if self in recreated:
return 'recreated'
elif self in ignored:
return 'ignored'
return ''
def get_purchases(self, name):
pool = Pool()
PurchaseLine = pool.get('purchase.line')
purchases = set()
for line in self.lines:
if isinstance(line.origin, PurchaseLine):
purchases.add(line.origin.purchase.id)
return list(purchases)
@classmethod
def search_purchases(cls, name, clause):
return [('lines.origin.purchase' + clause[0][len(name):],
*clause[1:3], 'purchase.line', *clause[3:])]
@classmethod
@process_purchase
def on_delete(cls, invoices):
return super().on_delete(invoices)
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, invoices):
for invoice in invoices:
if invoice.purchases and invoice.state == 'cancelled':
raise AccessError(
gettext('purchase.msg_purchase_invoice_reset_draft',
invoice=invoice.rec_name))
return super().draft(invoices)
@classmethod
@process_purchase
def _post(cls, invoices):
super()._post(invoices)
@classmethod
@process_purchase
def paid(cls, invoices):
super().paid(invoices)
@classmethod
@process_purchase
def cancel(cls, invoices):
super().cancel(invoices)
class InvoiceLine(metaclass=PoolMeta):
__name__ = 'account.invoice.line'
@classmethod
def __setup__(cls):
super().__setup__()
if not cls.origin.domain:
cls.origin.domain = {}
cls.origin.domain['purchase.line'] = [
('type', '=', Eval('type')),
]
@cached_property
def product_name(self):
pool = Pool()
PurchaseLine = pool.get('purchase.line')
name = super().product_name
if (isinstance(self.origin, PurchaseLine)
and self.origin.product_supplier):
name = self.origin.product_supplier.rec_name
return name
@fields.depends('origin')
def on_change_with_product_uom_category(self, name=None):
pool = Pool()
PurchaseLine = pool.get('purchase.line')
category = super().on_change_with_product_uom_category(name=name)
# Enforce the same unit category as they are used to compute the
# remaining quantity to invoice and the quantity to receive.
# Use getattr as reference field can have negative id
if (isinstance(self.origin, PurchaseLine)
and getattr(self.origin, 'unit', None)):
category = self.origin.unit.category
return category
def get_warehouse(self, name):
pool = Pool()
PurchaseLine = pool.get('purchase.line')
warehouse = super().get_warehouse(name)
if (not warehouse
and isinstance(self.origin, PurchaseLine)
and self.origin.purchase.warehouse):
warehouse = self.origin.purchase.warehouse.id
return warehouse
@property
def origin_name(self):
pool = Pool()
PurchaseLine = pool.get('purchase.line')
name = super().origin_name
if isinstance(self.origin, PurchaseLine) and self.origin.id >= 0:
name = self.origin.purchase.rec_name
return name
@classmethod
def _get_origin(cls):
models = super()._get_origin()
models.append('purchase.line')
return models
@classmethod
def on_delete(cls, lines):
pool = Pool()
Purchase = pool.get('purchase.purchase')
transaction = Transaction()
context = transaction.context
with without_check_access():
invoices = (l.invoice for l in cls.browse(lines)
if l.type == 'line' and l.invoice)
purchases = set(p for i in invoices for p in i.purchases)
if purchases:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Purchase.__queue__.process(purchases)
return super().on_delete(lines)

View File

@@ -0,0 +1,54 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.action.act_window" id="act_invoice_form">
<field name="name">Invoices</field>
<field name="res_model">account.invoice</field>
<field name="context"></field>
<field
name="domain"
eval="[('purchases.id', 'in', Eval('active_ids', []))]"
pyson="1"/>
</record>
<record model="ir.action.keyword"
id="act_open_invoice_keyword1">
<field name="keyword">form_relate</field>
<field name="model">purchase.purchase,-1</field>
<field name="action" ref="act_invoice_form"/>
</record>
<record model="ir.model.access" id="access_invoice_purchase">
<field name="model">account.invoice</field>
<field name="group" ref="group_purchase"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.action.act_window" id="act_invoice_line_form">
<field name="name">Invoice Lines</field>
<field name="res_model">account.invoice.line</field>
<field
name="domain"
eval="[('origin.id', 'in', Eval('active_ids', []), 'purchase.line')]"
pyson="1"/>
</record>
<record model="ir.action.keyword" id="act_invoice_line_form_keyword1">
<field name="keyword">form_relate</field>
<field name="model">purchase.line,-1</field>
<field name="action" ref="act_invoice_line_form"/>
</record>
<record model="ir.model.access" id="access_invoice_line_purchase">
<field name="model">account.invoice.line</field>
<field name="group" ref="group_purchase"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
</data>
</tryton>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,73 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data grouped="1">
<record model="ir.message" id="msg_change_purchase_uom">
<field name="text">You are trying to change the purchase unit of measure on which the purchase prices are based.</field>
</record>
<record model="ir.message" id="msg_erase_party_pending_purchase">
<field name="text">You cannot erase party "%(party)s" while they have pending purchases with company "%(company)s".</field>
</record>
<record model="ir.message" id="msg_warehouse_required_for_quotation">
<field name="text">To get a quote for the purchase "%(purchase)s" you must enter a warehouse for the line "%(line)s".</field>
</record>
<record model="ir.message" id="msg_purchase_delete_cancel">
<field name="text">To delete purchase "%(purchase)s" you must cancel it.</field>
</record>
<record model="ir.message" id="msg_purchase_supplier_location_required">
<field name="text">To process purchase "%(purchase)s" you must set a supplier location on party "%(party)s".</field>
</record>
<record model="ir.message" id="msg_purchase_product_missing_account_expense">
<field name="text">To invoice purchase "%(purchase)s" you must define an account expense for product "%(product)s".</field>
</record>
<record model="ir.message" id="msg_purchase_missing_account_expense">
<field name="text">To invoice purchase "%(purchase)s" you must configure a default account expense.</field>
</record>
<record model="ir.message" id="msg_purchase_line_delete_cancel_draft">
<field name="text">To delete line "%(line)s" you must cancel or reset to draft purchase "%(purchase)s".</field>
</record>
<record model="ir.message" id="msg_purchase_line_move_quantity">
<field name="text">The purchase line "%(line)s" is moving %(extra)s in addition to the %(quantity)s ordered.</field>
</record>
<record model="ir.message" id="msg_purchase_modify_header_draft">
<field name="text">To modify the header of purchase "%(purchase)s", it must be in draft state.</field>
</record>
<record model="ir.message" id="msg_purchase_invoice_reset_draft">
<field name="text">You cannot reset invoice "%(invoice)s" to draft because it was generated by a purchase.</field>
</record>
<record model="ir.message" id="msg_purchase_move_reset_draft">
<field name="text">You cannot reset move "%(move)s" to draft because it was generated by a purchase.</field>
</record>
<record model="ir.message" id="msg_purchase_line_create_draft">
<field name="text">You cannot add lines to purchase "%(purchase)s" because it is no longer in a draft state.</field>
</record>
<record model="ir.message" id="msg_purchase_line_tax_unique">
<field name="text">A tax can be added only once to a purchase line.</field>
</record>
<record model="ir.message" id="msg_purchase_reporting_company">
<field name="text">Company</field>
</record>
<record model="ir.message" id="msg_purchase_reporting_number">
<field name="text">#</field>
</record>
<record model="ir.message" id="msg_purchase_reporting_number_help">
<field name="text">Number of purchases.</field>
</record>
<record model="ir.message" id="msg_purchase_reporting_expense">
<field name="text">Expense</field>
</record>
<record model="ir.message" id="msg_purchase_reporting_expense_trend">
<field name="text">Expense Trend</field>
</record>
<record model="ir.message" id="msg_purchase_reporting_currency">
<field name="text">Currency</field>
</record>
<record model="ir.message" id="msg_purchase_reporting_date">
<field name="text">Date</field>
</record>
<record model="ir.message" id="msg_purchase_reporting_time_series">
<field name="text">Time Series</field>
</record>
</data>
</tryton>

98
modules/purchase/party.py Normal file
View File

@@ -0,0 +1,98 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.i18n import gettext
from trytond.model import ModelSQL, ValueMixin, fields
from trytond.modules.company.model import (
CompanyMultiValueMixin, CompanyValueMixin)
from trytond.modules.party.exceptions import EraseError
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval, TimeDelta
supplier_currency = fields.Many2One(
'currency.currency', "Supplier Currency", ondelete='RESTRICT',
help="Default currency for purchases from this party.")
supplier_lead_time = fields.TimeDelta(
"Lead Time",
domain=['OR',
('supplier_lead_time', '=', None),
('supplier_lead_time', '>=', TimeDelta()),
],
help="The time from confirming the purchase order to receiving "
"the goods from the party when used as a supplier.\n"
"Used if no lead time is set on the product supplier.")
class Party(CompanyMultiValueMixin, metaclass=PoolMeta):
__name__ = 'party.party'
customer_code = fields.MultiValue(fields.Char('Customer Code',
help="The code the party as supplier has assigned to the company"
" as customer."))
customer_codes = fields.One2Many(
'party.party.customer_code', 'party', "Customer Codes")
supplier_lead_time = fields.MultiValue(supplier_lead_time)
supplier_lead_times = fields.One2Many(
'party.party.supplier_lead_time', 'party', "Lead Times")
supplier_currency = fields.MultiValue(supplier_currency)
supplier_currencies = fields.One2Many(
'party.party.supplier_currency', 'party', "Supplier Currencies")
class CustomerCode(ModelSQL, CompanyValueMixin):
__name__ = 'party.party.customer_code'
party = fields.Many2One(
'party.party', "Party", ondelete='CASCADE',
context={
'company': Eval('company', -1),
},
depends={'company'})
customer_code = fields.Char('Customer Code')
class SupplierLeadTime(ModelSQL, CompanyValueMixin):
__name__ = 'party.party.supplier_lead_time'
party = fields.Many2One(
'party.party', "Party", ondelete='CASCADE',
context={
'company': Eval('company', -1),
},
depends={'company'})
supplier_lead_time = supplier_lead_time
class PartySupplierCurrency(ModelSQL, ValueMixin):
__name__ = 'party.party.supplier_currency'
party = fields.Many2One(
'party.party', "Party", ondelete='CASCADE')
supplier_currency = supplier_currency
class PartyReplace(metaclass=PoolMeta):
__name__ = 'party.replace'
@classmethod
def fields_to_replace(cls):
return super().fields_to_replace() + [
('purchase.product_supplier', 'party'),
('purchase.purchase', 'party'),
('purchase.purchase', 'invoice_party'),
]
class PartyErase(metaclass=PoolMeta):
__name__ = 'party.erase'
def check_erase_company(self, party, company):
pool = Pool()
Purchase = pool.get('purchase.purchase')
super().check_erase_company(party, company)
purchases = Purchase.search([
('party', '=', party.id),
('company', '=', company.id),
('state', 'not in', ['done', 'cancelled']),
])
if purchases:
raise EraseError(
gettext('purchase.msg_erase_party_pending_purchase',
party=party.rec_name,
company=company.rec_name))

View File

@@ -0,0 +1,26 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="party_view_form">
<field name="model">party.party</field>
<field name="inherit" ref="party.party_view_form"/>
<field name="name">party_form</field>
</record>
<record model="ir.model.field.access" id="access_party_supplier_currency">
<field name="model">party.party</field>
<field name="field">supplier_currency</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
</record>
<record model="ir.model.field.access" id="access_party_supplier_currency_group_sale">
<field name="model">party.party</field>
<field name="field">supplier_currency</field>
<field name="group" ref="group_purchase"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
</record>
</data>
</tryton>

554
modules/purchase/product.py Normal file
View File

@@ -0,0 +1,554 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime
from collections import defaultdict
from sql import Literal
from sql.aggregate import Count, Max
from trytond.i18n import gettext
from trytond.model import (
Index, MatchMixin, ModelSQL, ModelView, fields, sequence_ordered)
from trytond.modules.currency.fields import Monetary
from trytond.modules.product import (
ProductDeactivatableMixin, price_digits, round_price)
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval, If, TimeDelta
from trytond.tools import (
grouped_slice, is_full_text, lstrip_wildcard, reduce_ids)
from trytond.transaction import Transaction
from .exceptions import PurchaseUOMWarning
class Template(metaclass=PoolMeta):
__name__ = "product.template"
purchasable = fields.Boolean("Purchasable")
product_suppliers = fields.One2Many(
'purchase.product_supplier', 'template', "Suppliers",
states={
'invisible': (~Eval('purchasable', False)
| ~Eval('context', {}).get('company')),
})
purchase_uom = fields.Many2One(
'product.uom', "Purchase UoM",
states={
'invisible': ~Eval('purchasable'),
'required': Eval('purchasable', False),
},
domain=[('category', '=', Eval('default_uom_category', -1))],
help="The default Unit of Measure for purchases.")
@fields.depends('default_uom', 'purchase_uom', 'purchasable')
def on_change_default_uom(self):
try:
super().on_change_default_uom()
except AttributeError:
pass
if self.default_uom:
if self.purchase_uom:
if self.default_uom.category != self.purchase_uom.category:
self.purchase_uom = self.default_uom
else:
self.purchase_uom = self.default_uom
@classmethod
def view_attributes(cls):
return super().view_attributes() + [
('//page[@id="suppliers"]', 'states', {
'invisible': ~Eval('purchasable'),
})]
def product_suppliers_used(self, **pattern):
for product_supplier in self.product_suppliers:
if product_supplier.match(pattern):
yield product_supplier
@classmethod
def check_modification(cls, mode, templates, values=None, external=False):
pool = Pool()
Warning = pool.get('res.user.warning')
super().check_modification(
mode, templates, values=values, external=external)
if mode == 'write' and values.get("purchase_uom"):
for template in templates:
if not template.purchase_uom:
continue
if template.purchase_uom.id == values["purchase_uom"]:
continue
for product in template.products:
if not product.product_suppliers:
continue
name = '%s@product_template' % template.id
if Warning.check(name):
raise PurchaseUOMWarning(
name, gettext('purchase.msg_change_purchase_uom'))
@classmethod
def copy(cls, templates, default=None):
pool = Pool()
ProductSupplier = pool.get('purchase.product_supplier')
if default is None:
default = {}
else:
default = default.copy()
copy_suppliers = 'product_suppliers' not in default
default.setdefault('product_suppliers', None)
new_templates = super().copy(templates, default)
if copy_suppliers:
old2new = {}
to_copy = []
for template, new_template in zip(templates, new_templates):
to_copy.extend(
ps for ps in template.product_suppliers if not ps.product)
old2new[template.id] = new_template.id
if to_copy:
ProductSupplier.copy(to_copy, {
'template': lambda d: old2new[d['template']],
})
return new_templates
class Product(metaclass=PoolMeta):
__name__ = 'product.product'
product_suppliers = fields.One2Many(
'purchase.product_supplier', 'product', "Suppliers",
domain=[
('template', '=', Eval('template', -1)),
],
states={
'invisible': (~Eval('purchasable', False)
| ~Eval('context', {}).get('company')),
})
purchase_price_uom = fields.Function(fields.Numeric(
"Purchase Price", digits=price_digits), 'get_purchase_price_uom')
last_purchase_price_uom = fields.Function(fields.Numeric(
"Last Purchase Price", digits=price_digits),
'get_last_purchase_price_uom')
@classmethod
def get_purchase_price_uom(cls, products, name):
quantity = Transaction().context.get('quantity') or 0
return cls.get_purchase_price(products, quantity=quantity)
@classmethod
def get_last_purchase_price_uom(cls, products, name=None):
pool = Pool()
Company = pool.get('company.company')
Currency = pool.get('currency.currency')
Date = pool.get('ir.date')
Line = pool.get('purchase.line')
Purchase = pool.get('purchase.purchase')
UoM = pool.get('product.uom')
transaction = Transaction()
context = transaction.context
cursor = transaction.connection.cursor()
purchase = Purchase.__table__()
line = Line.__table__()
line_ids = []
where = purchase.state.in_(['confirmed', 'processing', 'done'])
supplier = context.get('supplier')
if supplier:
where &= purchase.party == supplier
for sub_products in grouped_slice(products):
query = (line
.join(purchase, condition=line.purchase == purchase.id)
.select(
Max(line.id),
where=where & reduce_ids(
line.product, map(int, sub_products)),
group_by=[line.product]))
cursor.execute(*query)
line_ids.extend(i for i, in cursor)
lines = Line.browse(line_ids)
uom = None
if context.get('uom'):
uom = UoM(context['uom'])
currency = None
if context.get('currency'):
currency = Currency(context['currency'])
elif context.get('company'):
currency = Company(context['company']).currency
date = context.get('purchase_date') or Date.today()
prices = defaultdict(lambda: None)
for line in lines:
default_uom = line.product.default_uom
if not uom or default_uom.category != uom.category:
product_uom = default_uom
else:
product_uom = uom
unit_price = UoM.compute_price(
line.unit, line.unit_price, product_uom)
if currency and line.purchase.currency != currency:
with Transaction().set_context(date=date):
unit_price = Currency.compute(
line.purchase.currency, unit_price, currency,
round=False)
prices[line.product.id] = round_price(unit_price)
return prices
def product_suppliers_used(self, **pattern):
for product_supplier in self.product_suppliers:
if product_supplier.match(pattern):
yield product_supplier
pattern['product'] = None
yield from self.template.product_suppliers_used(**pattern)
def _get_purchase_unit_price(self, quantity=0):
return
@classmethod
def get_purchase_price(cls, products, quantity=0):
'''
Return purchase price for product ids.
The context that can have as keys:
uom: the unit of measure
supplier: the supplier party id
currency: the currency id for the returned price
'''
pool = Pool()
Uom = pool.get('product.uom')
Company = pool.get('company.company')
Currency = pool.get('currency.currency')
Date = pool.get('ir.date')
ProductSupplier = pool.get('purchase.product_supplier')
ProductSupplierPrice = pool.get('purchase.product_supplier.price')
context = Transaction().context
prices = {}
assert len(products) == len(set(products)), "Duplicate products"
uom = None
if context.get('uom'):
uom = Uom(context['uom'])
currency = None
if context.get('currency'):
currency = Currency(context['currency'])
elif context.get('company'):
currency = Company(context['company']).currency
date = context.get('purchase_date') or Date.today()
last_purchase_prices = cls.get_last_purchase_price_uom(products)
for product in products:
unit_price = product._get_purchase_unit_price(quantity=quantity)
default_uom = product.default_uom
default_currency = currency
if not uom or default_uom.category != uom.category:
product_uom = default_uom
else:
product_uom = uom
pattern = ProductSupplier.get_pattern()
product_suppliers = product.product_suppliers_used(**pattern)
try:
product_supplier = next(product_suppliers)
except StopIteration:
pass
else:
pattern = ProductSupplierPrice.get_pattern()
for price in product_supplier.prices:
if price.match(quantity, product_uom, pattern):
unit_price = price.unit_price
default_uom = product_supplier.unit
default_currency = product_supplier.currency
if unit_price is not None:
unit_price = Uom.compute_price(
default_uom, unit_price, product_uom)
if currency and default_currency:
with Transaction().set_context(date=date):
unit_price = Currency.compute(
default_currency, unit_price, currency,
round=False)
if unit_price is None:
unit_price = last_purchase_prices[product.id]
else:
unit_price = round_price(unit_price)
prices[product.id] = unit_price
return prices
@classmethod
def copy(cls, products, default=None):
pool = Pool()
ProductSupplier = pool.get('purchase.product_supplier')
if default is None:
default = {}
else:
default = default.copy()
copy_suppliers = 'product_suppliers' not in default
if 'template' in default:
default.setdefault('product_suppliers', None)
new_products = super().copy(products, default)
if 'template' in default and copy_suppliers:
template2new = {}
product2new = {}
to_copy = []
for product, new_product in zip(products, new_products):
if product.product_suppliers:
to_copy.extend(product.product_suppliers)
template2new[product.template.id] = new_product.template.id
product2new[product.id] = new_product.id
if to_copy:
ProductSupplier.copy(to_copy, {
'product': lambda d: product2new[d['product']],
'template': lambda d: template2new[d['template']],
})
return new_products
class ProductSupplier(
sequence_ordered(), ProductDeactivatableMixin, MatchMixin,
ModelSQL, ModelView):
__name__ = 'purchase.product_supplier'
template = fields.Many2One(
'product.template', "Product",
required=True, ondelete='CASCADE',
domain=[
If(Bool(Eval('product')),
('products', '=', Eval('product')),
()),
],
states={
'readonly': Eval('id', -1) >= 0,
},
context={
'company': Eval('company', -1),
},
depends={'company'})
product = fields.Many2One(
'product.product', "Variant",
domain=[
If(Bool(Eval('template')),
('template', '=', Eval('template')),
()),
],
states={
'readonly': Eval('id', -1) >= 0,
},
context={
'company': Eval('company', -1),
},
depends={'company'})
party = fields.Many2One(
'party.party', 'Supplier', required=True, ondelete='CASCADE',
states={
'readonly': Eval('id', -1) >= 0,
},
context={
'company': Eval('company', -1),
},
depends={'company'})
name = fields.Char("Name", translate=True)
code = fields.Char("Code")
prices = fields.One2Many(
'purchase.product_supplier.price', 'product_supplier', "Prices",
help="Add price for different criteria.\n"
"The last matching line is used.")
company = fields.Many2One(
'company.company', "Company",
required=True, ondelete='CASCADE')
lead_time = fields.TimeDelta(
"Lead Time",
domain=['OR',
('lead_time', '=', None),
('lead_time', '>=', TimeDelta()),
],
help="The time from confirming the purchase order to receiving the "
"products.\n"
"If empty the lead time of the supplier is used.")
currency = fields.Many2One('currency.currency', 'Currency', required=True,
ondelete='RESTRICT')
unit = fields.Function(
fields.Many2One('product.uom', "Unit"), 'on_change_with_unit')
@classmethod
def __setup__(cls):
cls.code.search_unaccented = False
super().__setup__()
t = cls.__table__()
cls._sql_indexes.update({
Index(t, (t.code, Index.Similarity())),
})
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_currency():
Company = Pool().get('company.company')
if Transaction().context.get('company'):
company = Company(Transaction().context['company'])
return company.currency.id
@fields.depends(
'product', '_parent_product.template')
def on_change_product(self):
if self.product:
self.template = self.product.template
@fields.depends('party')
def on_change_party(self):
cursor = Transaction().connection.cursor()
self.currency = self.default_currency()
if self.party:
table = self.__table__()
cursor.execute(*table.select(table.currency,
where=table.party == self.party.id,
group_by=table.currency,
order_by=Count(Literal(1)).desc))
row = cursor.fetchone()
if row:
self.currency, = row
def get_rec_name(self, name):
if not self.name and not self.code:
if self.product:
name = self.product.rec_name
else:
name = self.template.rec_name
else:
if self.name:
name = self.name
elif self.product:
name = self.product.name
else:
name = self.template.name
if self.code:
name = '[' + self.code + ']' + name
return name
@classmethod
def search_rec_name(cls, name, clause):
_, operator, operand, *extra = clause
if operator.startswith('!') or operator.startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
code_value = operand
if operator.endswith('like') and is_full_text(operand):
code_value = lstrip_wildcard(operand)
domain = [bool_op,
('template.rec_name', operator, operand, *extra),
('product.rec_name', operator, operand, *extra),
('party.rec_name', operator, operand, *extra),
('code', operator, code_value, *extra),
('name', operator, operand, *extra),
]
return domain
@fields.depends(
'product', '_parent_product.purchase_uom',
'template', '_parent_template.purchase_uom')
def on_change_with_unit(self, name=None):
if self.product:
return self.product.purchase_uom
elif self.template:
return self.template.purchase_uom
@property
def lead_time_used(self):
# Use getattr because it can be called with an unsaved instance
lead_time = getattr(self, 'lead_time', None)
party = getattr(self, 'party', None)
if lead_time is None and party:
company = getattr(self, 'company', None)
lead_time = party.get_multivalue(
'supplier_lead_time', company=company.id if company else None)
return lead_time
def compute_supply_date(self, date=None):
'''
Compute the supply date for the Product Supplier at the given date
'''
Date = Pool().get('ir.date')
if not date:
with Transaction().set_context(company=self.company.id):
date = Date.today()
if self.lead_time_used is None:
return datetime.date.max
return date + self.lead_time_used
def compute_purchase_date(self, date):
'''
Compute the purchase date for the Product Supplier at the given date
'''
Date = Pool().get('ir.date')
if self.lead_time_used is None or date is None:
with Transaction().set_context(company=self.company.id):
return Date.today()
return date - self.lead_time_used
@staticmethod
def get_pattern():
context = Transaction().context
pattern = {'party': context.get('supplier')}
if 'product_supplier' in context:
pattern['id'] = context['product_supplier']
return pattern
class ProductSupplierPrice(
sequence_ordered(), ModelSQL, ModelView, MatchMixin):
__name__ = 'purchase.product_supplier.price'
product_supplier = fields.Many2One('purchase.product_supplier',
'Supplier', required=True, ondelete='CASCADE')
quantity = fields.Float(
"Quantity",
required=True,
domain=[('quantity', '>=', 0)],
help='Minimal quantity.')
unit_price = Monetary(
"Unit Price", currency='currency', required=True, digits=price_digits)
unit = fields.Function(fields.Many2One(
'product.uom', "Unit",
help="The unit in which the quantity is specified."),
'on_change_with_unit')
currency = fields.Function(
fields.Many2One('currency.currency', 'Currency'),
'on_change_with_currency')
@classmethod
def __setup__(cls):
super().__setup__()
cls.__access__.add('product_supplier')
@staticmethod
def default_quantity():
return 0.0
@fields.depends('product_supplier', '_parent_product_supplier.unit')
def on_change_with_unit(self, name=None):
return self.product_supplier.unit if self.product_supplier else None
@fields.depends('product_supplier', '_parent_product_supplier.currency')
def on_change_with_currency(self, name=None):
if self.product_supplier:
return self.product_supplier.currency
@staticmethod
def get_pattern():
return {}
def match(self, quantity, unit, pattern):
pool = Pool()
Uom = pool.get('product.uom')
test_quantity = Uom.compute_qty(
self.product_supplier.unit, self.quantity, unit)
if test_quantity > abs(quantity):
return False
return super().match(pattern)

View File

@@ -0,0 +1,24 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="template_view_form">
<field name="model">product.template</field>
<field name="inherit" ref="product.template_view_form"/>
<field name="name">template_form</field>
</record>
<record model="ir.ui.view" id="template_view_tree">
<field name="model">product.template</field>
<field name="inherit" ref="product.template_view_tree"/>
<field name="name">template_tree</field>
</record>
<record model="ir.ui.view" id="product_view_list_purchase_line">
<field name="model">product.product</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">product_list_purchase_line</field>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,954 @@
<?xml version="1.0" encoding="UTF-8"?>
<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
<office:meta><meta:generator>LibreOffice/7.6.4.1$Linux_X86_64 LibreOffice_project/60$Build-1</meta:generator><meta:creation-date>2008-06-07T15:28:22</meta:creation-date><dc:date>2009-01-10T16:03:32</dc:date><meta:editing-cycles>1</meta:editing-cycles><meta:editing-duration>PT0S</meta:editing-duration><meta:document-statistic meta:table-count="3" meta:image-count="0" meta:object-count="0" meta:page-count="8" meta:paragraph-count="101" meta:word-count="236" meta:character-count="2840" meta:non-whitespace-character-count="2704"/><meta:user-defined meta:name="Info 1"/><meta:user-defined meta:name="Info 2"/><meta:user-defined meta:name="Info 3"/><meta:user-defined meta:name="Info 4"/></office:meta>
<office:settings>
<config:config-item-set config:name="ooo:view-settings">
<config:config-item config:name="ViewAreaTop" config:type="long">0</config:config-item>
<config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item>
<config:config-item config:name="ViewAreaWidth" config:type="long">25269</config:config-item>
<config:config-item config:name="ViewAreaHeight" config:type="long">23973</config:config-item>
<config:config-item config:name="ShowRedlineChanges" config:type="boolean">true</config:config-item>
<config:config-item config:name="InBrowseMode" config:type="boolean">false</config:config-item>
<config:config-item-map-indexed config:name="Views">
<config:config-item-map-entry>
<config:config-item config:name="ViewId" config:type="string">view2</config:config-item>
<config:config-item config:name="ViewLeft" config:type="long">5660</config:config-item>
<config:config-item config:name="ViewTop" config:type="long">10248</config:config-item>
<config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item>
<config:config-item config:name="VisibleTop" config:type="long">0</config:config-item>
<config:config-item config:name="VisibleRight" config:type="long">25268</config:config-item>
<config:config-item config:name="VisibleBottom" config:type="long">23971</config:config-item>
<config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
<config:config-item config:name="ViewLayoutColumns" config:type="short">0</config:config-item>
<config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item>
<config:config-item config:name="ZoomFactor" config:type="short">100</config:config-item>
<config:config-item config:name="IsSelectedFrame" config:type="boolean">false</config:config-item>
<config:config-item config:name="KeepRatio" config:type="boolean">false</config:config-item>
<config:config-item config:name="AnchoredTextOverflowLegacy" config:type="boolean">false</config:config-item>
<config:config-item config:name="LegacySingleLineFontwork" config:type="boolean">false</config:config-item>
<config:config-item config:name="ConnectorUseSnapRect" config:type="boolean">false</config:config-item>
<config:config-item config:name="IgnoreBreakAfterMultilineField" config:type="boolean">false</config:config-item>
</config:config-item-map-entry>
</config:config-item-map-indexed>
</config:config-item-set>
<config:config-item-set config:name="ooo:configuration-settings">
<config:config-item config:name="PrintRightPages" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintProspectRTL" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintLeftPages" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintPaperFromSetup" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintControls" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintProspect" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintBlackFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintAnnotationMode" config:type="short">0</config:config-item>
<config:config-item config:name="PrintEmptyPages" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item>
<config:config-item config:name="AutoFirstLineIndentDisregardLineSpace" config:type="boolean">false</config:config-item>
<config:config-item config:name="HeaderSpacingBelowLastPara" config:type="boolean">false</config:config-item>
<config:config-item config:name="ProtectBookmarks" config:type="boolean">false</config:config-item>
<config:config-item config:name="ContinuousEndnotes" config:type="boolean">false</config:config-item>
<config:config-item config:name="DisableOffPagePositioning" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item>
<config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">true</config:config-item>
<config:config-item config:name="ApplyParagraphMarkFormatToNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintFaxName" config:type="string"/>
<config:config-item config:name="SurroundTextWrapSmall" config:type="boolean">false</config:config-item>
<config:config-item config:name="TreatSingleColumnBreakAsPageBreak" config:type="boolean">false</config:config-item>
<config:config-item config:name="PropLineSpacingShrinksFirstLine" config:type="boolean">false</config:config-item>
<config:config-item config:name="TabOverSpacing" config:type="boolean">false</config:config-item>
<config:config-item config:name="TabOverMargin" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmbedComplexScriptFonts" config:type="boolean">true</config:config-item>
<config:config-item config:name="EmbedLatinScriptFonts" config:type="boolean">true</config:config-item>
<config:config-item config:name="EmbedOnlyUsedFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmbedFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="ClippedPictures" config:type="boolean">false</config:config-item>
<config:config-item config:name="FrameAutowidthWithMorePara" config:type="boolean">false</config:config-item>
<config:config-item config:name="FloattableNomargins" config:type="boolean">false</config:config-item>
<config:config-item config:name="UnbreakableNumberings" config:type="boolean">false</config:config-item>
<config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item>
<config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item>
<config:config-item config:name="UseOldNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="RsidRoot" config:type="int">947783</config:config-item>
<config:config-item config:name="PrinterPaperFromSetup" config:type="boolean">false</config:config-item>
<config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/>
<config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item>
<config:config-item config:name="AddFrameOffsets" config:type="boolean">false</config:config-item>
<config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
<config:config-item config:name="Rsid" config:type="int">4032911</config:config-item>
<config:config-item config:name="FootnoteInColumnToPageEnd" config:type="boolean">true</config:config-item>
<config:config-item config:name="ProtectFields" config:type="boolean">false</config:config-item>
<config:config-item config:name="SaveGlobalDocumentLinks" config:type="boolean">false</config:config-item>
<config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item>
<config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item>
<config:config-item config:name="AddExternalLeading" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintGraphics" config:type="boolean">true</config:config-item>
<config:config-item config:name="EmbedSystemFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="IsLabelDocument" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddParaLineSpacingToTableCells" config:type="boolean">false</config:config-item>
<config:config-item config:name="UseFormerTextWrapping" config:type="boolean">false</config:config-item>
<config:config-item config:name="HyphenateURLs" config:type="boolean">true</config:config-item>
<config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item>
<config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item>
<config:config-item config:name="FieldAutoUpdate" config:type="boolean">true</config:config-item>
<config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item>
<config:config-item config:name="ChartAutoUpdate" config:type="boolean">true</config:config-item>
<config:config-item config:name="ImagePreferredDPI" config:type="int">0</config:config-item>
<config:config-item config:name="PrinterSetup" config:type="base64Binary"/>
<config:config-item config:name="SmallCapsPercentage66" config:type="boolean">true</config:config-item>
<config:config-item config:name="AlignTabStopPosition" config:type="boolean">true</config:config-item>
<config:config-item config:name="DropCapPunctuation" config:type="boolean">false</config:config-item>
<config:config-item config:name="MathBaselineAlignment" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrinterName" config:type="string"/>
<config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
<config:config-item config:name="AddParaTableSpacing" config:type="boolean">true</config:config-item>
<config:config-item config:name="DoNotJustifyLinesWithManualBreak" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintHiddenText" config:type="boolean">false</config:config-item>
<config:config-item config:name="IsKernAsianPunctuation" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrinterIndependentLayout" config:type="string">high-resolution</config:config-item>
<config:config-item config:name="TabOverflow" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddParaSpacingToTableCells" config:type="boolean">true</config:config-item>
<config:config-item config:name="AddVerticalFrameOffsets" config:type="boolean">false</config:config-item>
<config:config-item config:name="TabAtLeftIndentForParagraphsInList" config:type="boolean">false</config:config-item>
<config:config-item config:name="ApplyUserData" config:type="boolean">false</config:config-item>
<config:config-item config:name="MsWordCompMinLineHeightByFly" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintTextPlaceholder" config:type="boolean">false</config:config-item>
<config:config-item config:name="IgnoreFirstLineIndentInNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintPageBackground" config:type="boolean">true</config:config-item>
<config:config-item config:name="RedlineProtectionKey" config:type="base64Binary"/>
<config:config-item config:name="EmbedAsianScriptFonts" config:type="boolean">true</config:config-item>
<config:config-item config:name="BackgroundParaOverDrawings" config:type="boolean">false</config:config-item>
<config:config-item config:name="SaveThumbnail" config:type="boolean">true</config:config-item>
<config:config-item config:name="ConsiderTextWrapOnObjPos" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmbeddedDatabaseName" config:type="string"/>
<config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item>
<config:config-item config:name="DoNotResetParaAttrsForNumFont" config:type="boolean">false</config:config-item>
<config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmptyDbFieldHidesPara" config:type="boolean">false</config:config-item>
<config:config-item config:name="TableRowKeep" config:type="boolean">false</config:config-item>
<config:config-item config:name="NoNumberingShowFollowBy" config:type="boolean">false</config:config-item>
<config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item>
<config:config-item config:name="IgnoreTabsAndBlanksForLineCalculation" config:type="boolean">false</config:config-item>
<config:config-item config:name="DoNotCaptureDrawObjsOnPage" config:type="boolean">false</config:config-item>
<config:config-item config:name="GutterAtTop" config:type="boolean">false</config:config-item>
<config:config-item config:name="StylesNoDefault" config:type="boolean">false</config:config-item>
<config:config-item config:name="UnxForceZeroExtLeading" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintReversed" config:type="boolean">false</config:config-item>
<config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item>
<config:config-item config:name="CurrentDatabaseCommandType" config:type="int">0</config:config-item>
<config:config-item config:name="PrintDrawings" config:type="boolean">true</config:config-item>
<config:config-item config:name="OutlineLevelYieldsNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="CurrentDatabaseCommand" config:type="string"/>
<config:config-item config:name="CollapseEmptyCellPara" config:type="boolean">true</config:config-item>
</config:config-item-set>
</office:settings>
<office:scripts>
<office:script script:language="ooo:Basic">
<ooo:libraries xmlns:ooo="http://openoffice.org/2004/office" xmlns:xlink="http://www.w3.org/1999/xlink"/>
</office:script>
</office:scripts>
<office:font-face-decls>
<style:font-face style:name="Andale Sans UI" svg:font-family="&apos;Andale Sans UI&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
<style:font-face style:name="DejaVu Sans" svg:font-family="&apos;DejaVu Sans&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
<style:font-face style:name="Liberation Sans" svg:font-family="&apos;Liberation Sans&apos;" style:font-adornments="Regular" style:font-family-generic="swiss" style:font-pitch="variable"/>
<style:font-face style:name="Liberation Serif" svg:font-family="&apos;Liberation Serif&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="Liberation Serif1" svg:font-family="&apos;Liberation Serif&apos;" style:font-adornments="Bold" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="Liberation Serif2" svg:font-family="&apos;Liberation Serif&apos;" style:font-adornments="Regular" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="StarSymbol" svg:font-family="StarSymbol"/>
<style:font-face style:name="Thorndale AMT" svg:font-family="&apos;Thorndale AMT&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>
</office:font-face-decls>
<office:styles>
<style:default-style style:family="graphic">
<style:graphic-properties svg:stroke-color="#000000" draw:fill-color="#99ccff" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="false"/>
<style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" style:font-independent-line-spacing="false">
<style:tab-stops/>
</style:paragraph-properties>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Thorndale AMT" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Andale Sans UI" style:font-size-asian="10.5pt" style:language-asian="zxx" style:country-asian="none" style:font-name-complex="Andale Sans UI" style:font-size-complex="12pt" style:language-complex="zxx" style:country-complex="none"/>
</style:default-style>
<style:default-style style:family="paragraph">
<style:paragraph-properties fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="lr-tb"/>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Thorndale AMT" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Andale Sans UI" style:font-size-asian="10.5pt" style:language-asian="zxx" style:country-asian="none" style:font-name-complex="Andale Sans UI" style:font-size-complex="12pt" style:language-complex="zxx" style:country-complex="none" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/>
</style:default-style>
<style:default-style style:family="table">
<style:table-properties table:border-model="collapsing"/>
</style:default-style>
<style:default-style style:family="table-row">
<style:table-row-properties fo:keep-together="auto"/>
</style:default-style>
<style:style style:name="Standard" style:family="paragraph" style:class="text">
<style:text-properties style:font-name="Liberation Sans" fo:font-family="&apos;Liberation Sans&apos;" style:font-style-name="Regular" style:font-family-generic="swiss" style:font-pitch="variable" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
<style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" fo:keep-with-next="always"/>
<style:text-properties style:font-name="Liberation Serif2" fo:font-family="&apos;Liberation Serif&apos;" style:font-style-name="Regular" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="16pt" style:font-name-asian="DejaVu Sans" style:font-family-asian="&apos;DejaVu Sans&apos;" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="DejaVu Sans" style:font-family-complex="&apos;DejaVu Sans&apos;" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
</style:style>
<style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false"/>
<style:text-properties style:font-name="Liberation Sans" fo:font-family="&apos;Liberation Sans&apos;" style:font-style-name="Regular" style:font-family-generic="swiss" style:font-pitch="variable" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list">
<style:text-properties style:font-size-asian="12pt"/>
</style:style>
<style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties fo:margin-top="0.212cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
<style:text-properties fo:font-size="12pt" fo:font-style="italic" style:font-size-asian="12pt" style:font-style-asian="italic" style:font-size-complex="12pt" style:font-style-complex="italic"/>
</style:style>
<style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index">
<style:paragraph-properties text:number-lines="false" text:line-number="0"/>
<style:text-properties style:font-size-asian="12pt"/>
</style:style>
<style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="text">
<style:text-properties fo:font-size="16pt" fo:font-weight="bold" style:font-size-asian="115%" style:font-weight-asian="bold" style:font-size-complex="115%" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Table_20_Contents" style:display-name="Table Contents" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0"/>
<style:text-properties style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="Table_20_Heading" style:display-name="Table Heading" style:family="paragraph" style:parent-style-name="Table_20_Contents" style:class="extra" style:master-page-name="">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" style:page-number="auto" text:number-lines="false" text:line-number="0"/>
<style:text-properties style:font-name="Liberation Serif1" fo:font-family="&apos;Liberation Serif&apos;" style:font-style-name="Bold" style:font-family-generic="roman" style:font-pitch="variable" fo:font-weight="bold" style:font-size-asian="10.5pt" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Heading_20_2" style:display-name="Heading 2" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="text">
<style:text-properties fo:font-size="14pt" fo:font-style="italic" fo:font-weight="bold" style:font-size-asian="14pt" style:font-style-asian="italic" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-style-complex="italic" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Header_20_and_20_Footer" style:display-name="Header and Footer" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0">
<style:tab-stops>
<style:tab-stop style:position="8.5cm" style:type="center"/>
<style:tab-stop style:position="17cm" style:type="right"/>
</style:tab-stops>
</style:paragraph-properties>
</style:style>
<style:style style:name="Header" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0">
<style:tab-stops>
<style:tab-stop style:position="8.795cm" style:type="center"/>
<style:tab-stop style:position="17.59cm" style:type="right"/>
</style:tab-stops>
</style:paragraph-properties>
<style:text-properties fo:font-size="9pt" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="Footer" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0">
<style:tab-stops>
<style:tab-stop style:position="8.795cm" style:type="center"/>
<style:tab-stop style:position="17.59cm" style:type="right"/>
</style:tab-stops>
</style:paragraph-properties>
<style:text-properties fo:font-size="9pt" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="Heading_20_3" style:display-name="Heading 3" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="text">
<style:text-properties fo:font-size="14pt" fo:font-weight="bold" style:font-size-asian="14pt" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Text_20_body_20_indent" style:display-name="Text body indent" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="text">
<style:paragraph-properties fo:margin-left="0.499cm" fo:margin-right="0cm" fo:text-indent="0cm" style:auto-text-indent="false"/>
</style:style>
<style:style style:name="Text" style:family="paragraph" style:parent-style-name="Caption" style:class="extra"/>
<style:style style:name="Quotations" style:family="paragraph" style:parent-style-name="Standard" style:class="html">
<style:paragraph-properties fo:margin-left="1cm" fo:margin-right="1cm" fo:margin-top="0cm" fo:margin-bottom="0.499cm" style:contextual-spacing="false" fo:text-indent="0cm" style:auto-text-indent="false"/>
</style:style>
<style:style style:name="Title" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="chapter">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties fo:font-size="28pt" fo:font-weight="bold" style:font-size-asian="28pt" style:font-weight-asian="bold" style:font-size-complex="28pt" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Subtitle" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="chapter">
<style:paragraph-properties fo:margin-top="0.106cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties fo:font-size="18pt" style:font-size-asian="18pt" style:font-size-complex="18pt"/>
</style:style>
<style:style style:name="Frame_20_contents" style:display-name="Frame contents" style:family="paragraph" style:parent-style-name="Standard" style:class="extra"/>
<style:style style:name="Placeholder" style:family="text">
<style:text-properties fo:font-variant="small-caps" fo:color="#008080" loext:opacity="100%" style:text-underline-style="dotted" style:text-underline-width="auto" style:text-underline-color="font-color"/>
</style:style>
<style:style style:name="Bullet_20_Symbols" style:display-name="Bullet Symbols" style:family="text">
<style:text-properties style:font-name="StarSymbol" fo:font-family="StarSymbol" fo:font-size="9pt" style:font-name-asian="StarSymbol" style:font-family-asian="StarSymbol" style:font-size-asian="9pt" style:font-name-complex="StarSymbol" style:font-family-complex="StarSymbol" style:font-size-complex="9pt"/>
</style:style>
<style:style style:name="Frame" style:family="graphic">
<style:graphic-properties text:anchor-type="paragraph" svg:x="0cm" svg:y="0cm" fo:margin-left="0.201cm" fo:margin-right="0.201cm" fo:margin-top="0.201cm" fo:margin-bottom="0.201cm" style:wrap="parallel" style:number-wrapped-paragraphs="no-limit" style:wrap-contour="false" style:vertical-pos="top" style:vertical-rel="paragraph-content" style:horizontal-pos="center" style:horizontal-rel="paragraph-content" fo:background-color="transparent" draw:fill="none" fo:padding="0.15cm" fo:border="0.06pt solid #000000"/>
</style:style>
<text:outline-style style:name="Outline">
<text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
</text:outline-style>
<text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
<text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
<text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
<loext:theme loext:name="Office Theme">
<loext:theme-colors loext:name="LibreOffice">
<loext:color loext:name="dark1" loext:color="#000000"/>
<loext:color loext:name="light1" loext:color="#ffffff"/>
<loext:color loext:name="dark2" loext:color="#000000"/>
<loext:color loext:name="light2" loext:color="#ffffff"/>
<loext:color loext:name="accent1" loext:color="#18a303"/>
<loext:color loext:name="accent2" loext:color="#0369a3"/>
<loext:color loext:name="accent3" loext:color="#a33e03"/>
<loext:color loext:name="accent4" loext:color="#8e03a3"/>
<loext:color loext:name="accent5" loext:color="#c99c00"/>
<loext:color loext:name="accent6" loext:color="#c9211e"/>
<loext:color loext:name="hyperlink" loext:color="#0000ee"/>
<loext:color loext:name="followed-hyperlink" loext:color="#551a8b"/>
</loext:theme-colors>
</loext:theme>
</office:styles>
<office:automatic-styles>
<style:style style:name="Table1" style:family="table">
<style:table-properties style:width="17.588cm" table:align="left"/>
</style:style>
<style:style style:name="Table1.A" style:family="table-column">
<style:table-column-properties style:column-width="6.962cm"/>
</style:style>
<style:style style:name="Table1.B" style:family="table-column">
<style:table-column-properties style:column-width="2.656cm"/>
</style:style>
<style:style style:name="Table1.A1" style:family="table-cell">
<style:table-cell-properties fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="0.05pt solid #000000" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table1.E1" style:family="table-cell">
<style:table-cell-properties fo:background-color="#cccccc" fo:padding="0.097cm" fo:border="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table1.A2" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.A3" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.A4" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.A5" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.B5" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.C5" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.D5" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.E5" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.A6" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.A7" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.A8" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.E8" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.A9" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.A10" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.A11" style:family="table-cell">
<style:table-cell-properties fo:background-color="#e6e6e6" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table1.A12" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.A13" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.A14" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.A15" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.A16" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table1.A17" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table2" style:family="table">
<style:table-properties style:width="17.59cm" table:align="margins" style:may-break-between-rows="false"/>
</style:style>
<style:style style:name="Table2.A" style:family="table-column">
<style:table-column-properties style:column-width="8.795cm" style:rel-column-width="32767*"/>
</style:style>
<style:style style:name="Table2.B" style:family="table-column">
<style:table-column-properties style:column-width="8.795cm" style:rel-column-width="32768*"/>
</style:style>
<style:style style:name="Table4" style:family="table">
<style:table-properties style:width="7.969cm" table:align="right" style:may-break-between-rows="true"/>
</style:style>
<style:style style:name="Table4.A" style:family="table-column">
<style:table-column-properties style:column-width="5.308cm"/>
</style:style>
<style:style style:name="Table4.B" style:family="table-column">
<style:table-column-properties style:column-width="2.662cm"/>
</style:style>
<style:style style:name="Table4.A1" style:family="table-cell">
<style:table-cell-properties fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="0.05pt solid #000000" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table4.B1" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table4.A2" style:family="table-cell">
<style:table-cell-properties fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table4.B2" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table4.B3" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table4" style:family="table">
<style:table-properties style:width="7.969cm" table:align="right" style:may-break-between-rows="true"/>
</style:style>
<style:style style:name="Table4.A" style:family="table-column">
<style:table-column-properties style:column-width="5.308cm"/>
</style:style>
<style:style style:name="Table4.B" style:family="table-column">
<style:table-column-properties style:column-width="2.662cm"/>
</style:style>
<style:style style:name="Table4.A1" style:family="table-cell">
<style:table-cell-properties fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="0.05pt solid #000000" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table4.B1" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table4.A2" style:family="table-cell">
<style:table-cell-properties fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table4.B2" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Table4.B3" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="P1" style:family="paragraph" style:parent-style-name="Header">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties officeooo:paragraph-rsid="0011dc8a"/>
</style:style>
<style:style style:name="P2" style:family="paragraph" style:parent-style-name="Footer">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties officeooo:paragraph-rsid="0011dc8a"/>
</style:style>
<style:style style:name="P3" style:family="paragraph" style:parent-style-name="Header">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
<style:text-properties fo:font-size="12pt" officeooo:paragraph-rsid="003d898f" style:font-size-asian="12pt" style:font-size-complex="12pt"/>
</style:style>
<style:style style:name="P4" style:family="paragraph" style:parent-style-name="Frame_20_contents">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P5" style:family="paragraph" style:parent-style-name="Header">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties officeooo:paragraph-rsid="0011dc8a"/>
</style:style>
<style:style style:name="P6" style:family="paragraph" style:parent-style-name="Footer">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties officeooo:paragraph-rsid="0011dc8a"/>
</style:style>
<style:style style:name="P7" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P8" style:family="paragraph" style:parent-style-name="Header">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
<style:text-properties fo:font-size="12pt" officeooo:paragraph-rsid="003c73bf" style:font-size-asian="12pt" style:font-size-complex="12pt"/>
</style:style>
<style:style style:name="P9" style:family="paragraph" style:parent-style-name="Header">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
<style:text-properties fo:font-size="12pt" officeooo:paragraph-rsid="0011dc8a" style:font-size-asian="12pt" style:font-size-complex="12pt"/>
</style:style>
<style:style style:name="P10" style:family="paragraph" style:parent-style-name="Frame_20_contents">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P11" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P12" style:family="paragraph" style:parent-style-name="Heading_20_2">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P13" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-left="11.28cm" fo:margin-right="0cm" fo:text-indent="0cm" style:auto-text-indent="false"/>
</style:style>
<style:style style:name="P14" style:family="paragraph" style:parent-style-name="Standard" style:master-page-name="">
<style:paragraph-properties fo:margin-left="11.28cm" fo:margin-right="0cm" fo:text-indent="0cm" style:auto-text-indent="false" style:page-number="auto" fo:break-before="page"/>
</style:style>
<style:style style:name="P15" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-left="11.28cm" fo:margin-right="0cm" fo:text-indent="0cm" style:auto-text-indent="false"/>
<style:text-properties officeooo:paragraph-rsid="002b1070"/>
</style:style>
<style:style style:name="P16" style:family="paragraph" style:parent-style-name="Standard" style:master-page-name="">
<style:paragraph-properties fo:margin-left="11.28cm" fo:margin-right="0cm" fo:text-indent="0cm" style:auto-text-indent="false" style:page-number="auto" fo:break-before="page"/>
<style:text-properties officeooo:paragraph-rsid="002b1070"/>
</style:style>
<style:style style:name="P17" style:family="paragraph" style:parent-style-name="Table_20_Heading">
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P18" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P19" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false"/>
<style:text-properties officeooo:paragraph-rsid="00153e37"/>
</style:style>
<style:style style:name="P20" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
<style:text-properties style:text-underline-style="none"/>
</style:style>
<style:style style:name="P21" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:text-properties officeooo:rsid="0013c923" officeooo:paragraph-rsid="0013c923"/>
</style:style>
<style:style style:name="P22" style:family="paragraph" style:parent-style-name="Text_20_body" style:master-page-name="">
<style:paragraph-properties style:page-number="auto" fo:keep-with-next="always"/>
<style:text-properties officeooo:rsid="0013c923" officeooo:paragraph-rsid="0013c923"/>
</style:style>
<style:style style:name="P23" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:text-properties officeooo:paragraph-rsid="003adf68"/>
</style:style>
<style:style style:name="P24" style:family="paragraph" style:parent-style-name="Standard" style:master-page-name="">
<style:paragraph-properties style:page-number="auto" fo:break-before="auto" fo:break-after="auto"/>
</style:style>
<style:style style:name="P25" style:family="paragraph" style:parent-style-name="Text_20_body" style:master-page-name="">
<style:paragraph-properties style:page-number="auto" fo:keep-with-next="always"/>
</style:style>
<style:style style:name="P26" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:text-indent="0cm" style:auto-text-indent="false"/>
<style:text-properties style:font-name="Liberation Serif"/>
</style:style>
<style:style style:name="P27" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:text-align="start" style:justify-single-word="false" fo:text-indent="0cm" style:auto-text-indent="false"/>
<style:text-properties style:font-name="Liberation Serif"/>
</style:style>
<style:style style:name="P28" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:text-indent="0cm" style:auto-text-indent="false"/>
<style:text-properties style:font-name="Liberation Serif" officeooo:paragraph-rsid="002cc9ef"/>
</style:style>
<style:style style:name="P29" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:text-indent="0cm" style:auto-text-indent="false"/>
<style:text-properties style:font-name="Liberation Serif" officeooo:paragraph-rsid="002df6ba"/>
</style:style>
<style:style style:name="P30" style:family="paragraph" style:parent-style-name="Heading_20_1">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"/>
</style:style>
<style:style style:name="P31" style:family="paragraph" style:parent-style-name="Text_20_body">
<loext:graphic-properties draw:fill="none"/>
<style:paragraph-properties fo:margin-left="1cm" fo:margin-right="0cm" fo:margin-top="0cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" fo:text-indent="0cm" style:auto-text-indent="false" fo:background-color="transparent"/>
<style:text-properties officeooo:rsid="0019f6b5" officeooo:paragraph-rsid="0019f6b5"/>
</style:style>
<style:style style:name="P32" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:break-before="column"/>
<style:text-properties officeooo:rsid="001bf6f1" officeooo:paragraph-rsid="0024fff1"/>
</style:style>
<style:style style:name="P33" style:family="paragraph" style:parent-style-name="Standard" style:master-page-name="">
<style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:text-indent="0cm" style:auto-text-indent="false" style:page-number="auto" fo:break-before="page"/>
<style:text-properties fo:font-size="6pt" officeooo:paragraph-rsid="002b1070" style:font-size-asian="5.25pt" style:font-size-complex="6pt"/>
</style:style>
<style:style style:name="P34" style:family="paragraph" style:parent-style-name="Frame_20_contents">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P35" style:family="paragraph" style:parent-style-name="Header">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
<style:text-properties fo:font-size="12pt" officeooo:paragraph-rsid="003d898f" style:font-size-asian="12pt" style:font-size-complex="12pt"/>
</style:style>
<style:style style:name="T1" style:family="text">
<style:text-properties officeooo:rsid="0029e76e"/>
</style:style>
<style:style style:name="T2" style:family="text">
<style:text-properties officeooo:rsid="00381553"/>
</style:style>
<style:style style:name="T3" style:family="text">
<style:text-properties officeooo:rsid="0039ae4c"/>
</style:style>
<style:style style:name="T4" style:family="text">
<style:text-properties officeooo:rsid="003adf68"/>
</style:style>
<style:style style:name="fr1" style:family="graphic" style:parent-style-name="Frame">
<style:graphic-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-top="0cm" fo:margin-bottom="0cm" style:wrap="parallel" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="middle" style:vertical-rel="baseline" style:horizontal-pos="left" style:horizontal-rel="paragraph" draw:opacity="100%" fo:padding="0cm" fo:border="none" draw:wrap-influence-on-position="once-concurrent" loext:allow-overlap="true">
<style:columns fo:column-count="1" fo:column-gap="0cm"/>
</style:graphic-properties>
</style:style>
<style:style style:name="Sect1" style:family="section">
<style:section-properties text:dont-balance-text-columns="true" style:editable="false">
<style:columns fo:column-count="2" fo:column-gap="0cm">
<style:column style:rel-width="32767*" fo:start-indent="0cm" fo:end-indent="0cm"/>
<style:column style:rel-width="32768*" fo:start-indent="0cm" fo:end-indent="0cm"/>
</style:columns>
</style:section-properties>
</style:style>
<style:page-layout style:name="pm1">
<style:page-layout-properties fo:page-width="21.59cm" fo:page-height="27.94cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="44" style:layout-grid-base-height="0.55cm" style:layout-grid-ruby-height="0cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="true" style:layout-grid-display="true" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
<style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="none" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
</style:page-layout-properties>
<style:header-style>
<style:header-footer-properties fo:min-height="0cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-bottom="0.499cm"/>
</style:header-style>
<style:footer-style>
<style:header-footer-properties fo:min-height="0cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-top="0.499cm"/>
</style:footer-style>
</style:page-layout>
<style:style style:name="dp1" style:family="drawing-page">
<style:drawing-page-properties draw:background-size="full"/>
</style:style>
</office:automatic-styles>
<office:master-styles>
<style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1">
<style:header>
<text:p text:style-name="P1"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;company and company.header&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P1"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;line in company.header_used.split(&apos;\n&apos;)&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P1"><text:placeholder text:placeholder-type="text">&lt;line&gt;</text:placeholder></text:p>
<text:p text:style-name="P1"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
<text:p text:style-name="P2"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
<text:p text:style-name="P3"><text:placeholder text:placeholder-type="text">&lt;choose&gt;</text:placeholder></text:p>
<text:p text:style-name="P3"><text:placeholder text:placeholder-type="text">&lt;when test=&quot;company and company.logo&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P3"><draw:frame draw:style-name="fr1" draw:name="image:company.get_logo_cm(7, 3.5)" text:anchor-type="as-char" svg:width="7.001cm" draw:z-index="7">
<draw:text-box fo:min-height="3cm">
<text:p text:style-name="P4"/>
</draw:text-box>
</draw:frame></text:p>
<text:p text:style-name="P3"><text:placeholder text:placeholder-type="text">&lt;/when&gt;</text:placeholder></text:p>
<text:p text:style-name="P3"><text:placeholder text:placeholder-type="text">&lt;when test=&quot;company&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P3"><text:placeholder text:placeholder-type="text">&lt;company.rec_name&gt;</text:placeholder></text:p>
<text:p text:style-name="P3"><text:placeholder text:placeholder-type="text">&lt;/when&gt;</text:placeholder></text:p>
<text:p text:style-name="P3"><text:placeholder text:placeholder-type="text">&lt;/choose&gt;</text:placeholder></text:p>
</style:header>
<style:footer>
<text:p text:style-name="P2"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;company and company.footer&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P2"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;line in company.footer_used.split(&apos;\n&apos;)&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P2"><text:placeholder text:placeholder-type="text">&lt;line&gt;</text:placeholder></text:p>
<text:p text:style-name="P2"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
<text:p text:style-name="P2"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
</style:footer>
</style:master-page>
</office:master-styles>
<office:body>
<office:text text:use-soft-page-breaks="true">
<office:forms form:automatic-focus="false" form:apply-design-mode="false"/>
<text:sequence-decls>
<text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
<text:sequence-decl text:display-outline-level="0" text:name="Table"/>
<text:sequence-decl text:display-outline-level="0" text:name="Text"/>
<text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
<text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
</text:sequence-decls>
<text:p text:style-name="P24"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;purchase in records&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P33"/>
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text">&lt;replace text:p=&quot;set_lang(purchase.party.lang)&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text">&lt;replace text:p=&quot;purchase.set_lang(purchase.party.lang)&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P13"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;line in purchase.report_address.splitlines()&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P13"><text:placeholder text:placeholder-type="text">&lt;line&gt;</text:placeholder></text:p>
<text:p text:style-name="P13"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
<text:p text:style-name="P13"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;purchase.party.tax_identifier&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P13"><text:placeholder text:placeholder-type="text">&lt;purchase.party.tax_identifier.type_string&gt;</text:placeholder>: <text:placeholder text:placeholder-type="text">&lt;purchase.party.tax_identifier.code&gt;</text:placeholder></text:p>
<text:p text:style-name="P13"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
<text:p text:style-name="P11"><text:placeholder text:placeholder-type="text">&lt;choose test=&quot;&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P20"><text:placeholder text:placeholder-type="text">&lt;when test=&quot;purchase.state == &apos;draft&apos;&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P30">Draft Purchase Order</text:p>
<text:p text:style-name="P20"><text:placeholder text:placeholder-type="text">&lt;/when&gt;</text:placeholder></text:p>
<text:p text:style-name="P20"><text:soft-page-break/><text:placeholder text:placeholder-type="text">&lt;when test=&quot;purchase.state == &apos;quotation&apos;&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P30">Request for Quotation No: <text:placeholder text:placeholder-type="text">&lt;purchase.full_number&gt;</text:placeholder></text:p>
<text:p text:style-name="P20"><text:placeholder text:placeholder-type="text">&lt;/when&gt;</text:placeholder></text:p>
<text:p text:style-name="P20"><text:placeholder text:placeholder-type="text">&lt;otherwise test=&quot;&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P30">Purchase Order No: <text:placeholder text:placeholder-type="text">&lt;purchase.full_number&gt;</text:placeholder></text:p>
<text:p text:style-name="P20"><text:placeholder text:placeholder-type="text">&lt;/otherwise&gt;</text:placeholder></text:p>
<text:p text:style-name="P20"><text:placeholder text:placeholder-type="text">&lt;/choose&gt;</text:placeholder></text:p>
<text:section text:style-name="Sect1" text:name="Section1">
<text:p text:style-name="P25">Description: <text:placeholder text:placeholder-type="text">&lt;purchase.description or &apos;&apos;&gt;</text:placeholder></text:p>
<text:p text:style-name="P22">Reference: <text:placeholder text:placeholder-type="text">&lt;purchase.reference or &apos;&apos;&gt;</text:placeholder></text:p>
<text:p text:style-name="P23">Customer Code: <text:placeholder text:placeholder-type="text">&lt;purchase.party.get_multivalue(&apos;customer_code&apos;, company=purchase.company.id) or &apos;&apos;&gt;</text:placeholder></text:p>
<text:p text:style-name="Text_20_body">Date: <text:placeholder text:placeholder-type="text">&lt;format_date(purchase.purchase_date or today, purchase.party.lang)&gt;</text:placeholder></text:p>
<text:p text:style-name="P32">Delivery Address:</text:p>
<text:p text:style-name="P31"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;line in purchase.delivery_full_address.splitlines()&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P31"><text:placeholder text:placeholder-type="text">&lt;line&gt;</text:placeholder></text:p>
<text:p text:style-name="P31"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
</text:section>
<table:table table:name="Table1" table:style-name="Table1">
<table:table-column table:style-name="Table1.A"/>
<table:table-column table:style-name="Table1.B" table:number-columns-repeated="4"/>
<table:table-header-rows>
<table:table-row>
<table:table-cell table:style-name="Table1.A1" office:value-type="string">
<text:p text:style-name="Table_20_Heading">Description</text:p>
</table:table-cell>
<table:table-cell table:style-name="Table1.A1" office:value-type="string">
<text:p text:style-name="Table_20_Heading">Quantity</text:p>
</table:table-cell>
<table:table-cell table:style-name="Table1.A1" office:value-type="string">
<text:p text:style-name="Table_20_Heading">Unit Price</text:p>
</table:table-cell>
<table:table-cell table:style-name="Table1.A1" office:value-type="string">
<text:p text:style-name="Table_20_Heading">Taxes</text:p>
</table:table-cell>
<table:table-cell table:style-name="Table1.E1" office:value-type="string">
<text:p text:style-name="Table_20_Heading">Amount</text:p>
</table:table-cell>
</table:table-row>
</table:table-header-rows>
<table:table-row>
<table:table-cell table:style-name="Table1.A2" table:number-columns-spanned="5" office:value-type="string">
<text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;line in purchase.lines&quot;&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Table1.A2" table:number-columns-spanned="5" office:value-type="string">
<text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;choose test=&quot;&quot;&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<text:soft-page-break/>
<table:table-row>
<table:table-cell table:style-name="Table1.A2" table:number-columns-spanned="5" office:value-type="string">
<text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;when test=&quot;line.type == &apos;line&apos;&quot;&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Table1.A5" office:value-type="string">
<text:p text:style-name="P28"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;line.product_name&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P28"><text:placeholder text:placeholder-type="text">&lt;line.product_name&gt;</text:placeholder></text:p>
<text:p text:style-name="P28"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
<text:p text:style-name="P28"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;line.description&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P28"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;description in line.description.split(&apos;\n&apos;)&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P28"><text:placeholder text:placeholder-type="text">&lt;description&gt;</text:placeholder></text:p>
<text:p text:style-name="P28"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
<text:p text:style-name="P28"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
</table:table-cell>
<table:table-cell table:style-name="Table1.B5" office:value-type="string">
<text:p text:style-name="P18"><text:placeholder text:placeholder-type="text">&lt;format_number_symbol(line.quantity, purchase.party.lang, line.unit, digits=line.unit.digits) if line.unit else format_number(line.quantity, purchase.party.lang)&gt;</text:placeholder></text:p>
</table:table-cell>
<table:table-cell table:style-name="Table1.B5" office:value-type="string">
<text:p text:style-name="P19"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;line.unit_price is not None&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P19"><text:placeholder text:placeholder-type="text">&lt;format_currency(line.unit_price, purchase.party.lang, purchase.currency, digits=line.__class__.unit_price.digits[1])&gt;</text:placeholder></text:p>
<text:p text:style-name="P19"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
</table:table-cell>
<table:table-cell table:style-name="Table1.B5" office:value-type="string">
<text:p text:style-name="P7"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;tax in line.taxes&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P7"><text:placeholder text:placeholder-type="text">&lt;tax.description&gt;</text:placeholder></text:p>
<text:p text:style-name="P7"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
</table:table-cell>
<table:table-cell table:style-name="Table1.E5" office:value-type="string">
<text:p text:style-name="P18"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;line.amount is not None&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P18"><text:placeholder text:placeholder-type="text">&lt;format_currency(line.amount, purchase.party.lang, purchase.currency)&gt;</text:placeholder></text:p>
<text:p text:style-name="P18"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
</table:table-cell>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Table1.A2" table:number-columns-spanned="5" office:value-type="string">
<text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;/when&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Table1.A2" table:number-columns-spanned="5" office:value-type="string">
<text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;when test=&quot;line.type == &apos;subtotal&apos;&quot;&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Table1.A8" table:number-columns-spanned="4" office:value-type="string">
<text:p text:style-name="Heading_20_2"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;description in (line.description or &apos;&apos;).split(&apos;\n&apos;)&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="Heading_20_2"><text:soft-page-break/><text:placeholder text:placeholder-type="text">&lt;description&gt;</text:placeholder></text:p>
<text:p text:style-name="P12"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:table-cell table:style-name="Table1.E5" office:value-type="string">
<text:p text:style-name="P18"><text:placeholder text:placeholder-type="text">&lt;format_currency(line.amount, purchase.party.lang, purchase.currency)&gt;</text:placeholder><text:soft-page-break/></text:p>
</table:table-cell>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Table1.A2" table:number-columns-spanned="5" office:value-type="string">
<text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;/when&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Table1.A2" table:number-columns-spanned="5" office:value-type="string">
<text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;when test=&quot;line.type == &apos;title&apos;&quot;&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Table1.A11" table:number-columns-spanned="5" office:value-type="string">
<text:p text:style-name="Heading_20_2"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;description in (line.description or &apos;&apos;).split(&apos;\n&apos;)&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="Heading_20_2"><text:placeholder text:placeholder-type="text">&lt;description&gt;</text:placeholder></text:p>
<text:p text:style-name="Heading_20_2"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Table1.A2" table:number-columns-spanned="5" office:value-type="string">
<text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;/when&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Table1.A2" table:number-columns-spanned="5" office:value-type="string">
<text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;otherwise test=&quot;&quot;&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Table1.A2" table:number-columns-spanned="5" office:value-type="string">
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;description in (line.description or &apos;&apos;).split(&apos;\n&apos;)&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text">&lt;description&gt;</text:placeholder></text:p>
<text:p text:style-name="P27"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Table1.A2" table:number-columns-spanned="5" office:value-type="string">
<text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;/otherwise&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Table1.A2" table:number-columns-spanned="5" office:value-type="string">
<text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;/choose&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<text:soft-page-break/>
<table:table-row>
<table:table-cell table:style-name="Table1.A2" table:number-columns-spanned="5" office:value-type="string">
<text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
</table:table>
<text:p text:style-name="Text_20_body"/>
<table:table table:name="Table2" table:style-name="Table2">
<table:table-column table:style-name="Table2.A"/>
<table:table-column table:style-name="Table2.B"/>
<text:soft-page-break/>
<table:table-row>
<table:table-cell office:value-type="string">
<text:p text:style-name="Table_20_Contents"/>
</table:table-cell>
<table:table-cell office:value-type="string">
<table:table table:name="Table4" table:style-name="Table4">
<table:table-column table:style-name="Table4.A"/>
<table:table-column table:style-name="Table4.B"/>
<table:table-row>
<table:table-cell table:style-name="Table4.A1" office:value-type="string">
<text:p text:style-name="P17">Total (excl. taxes):</text:p>
</table:table-cell>
<table:table-cell table:style-name="Table4.B1" office:value-type="string">
<text:p text:style-name="P18"><text:placeholder text:placeholder-type="text">&lt;format_currency(purchase.untaxed_amount, purchase.party.lang, purchase.currency)&gt;</text:placeholder></text:p>
</table:table-cell>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Table4.A2" office:value-type="string">
<text:p text:style-name="P17">Taxes:</text:p>
</table:table-cell>
<table:table-cell table:style-name="Table4.B2" office:value-type="string">
<text:p text:style-name="P18"><text:placeholder text:placeholder-type="text">&lt;format_currency(purchase.tax_amount, purchase.party.lang, purchase.currency)&gt;</text:placeholder></text:p>
</table:table-cell>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Table4.A2" office:value-type="string">
<text:p text:style-name="P17">Total:</text:p>
</table:table-cell>
<table:table-cell table:style-name="Table4.B2" office:value-type="string">
<text:p text:style-name="P18"><text:placeholder text:placeholder-type="text">&lt;format_currency(purchase.total_amount, purchase.party.lang, purchase.currency)&gt;</text:placeholder></text:p>
</table:table-cell>
</table:table-row>
</table:table>
<text:p text:style-name="Table_20_Contents"/>
</table:table-cell>
</table:table-row>
</table:table>
<text:p text:style-name="Text_20_body"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;comment in (purchase.comment or &apos;&apos;).split(&apos;\n&apos;)&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="Text_20_body"><text:soft-page-break/><text:placeholder text:placeholder-type="text">&lt;comment&gt;</text:placeholder></text:p>
<text:p text:style-name="Text_20_body"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
<text:p text:style-name="Text_20_body"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
</office:text>
</office:body>
</office:document>

2447
modules/purchase/purchase.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,653 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="res.group" id="group_purchase">
<field name="name">Purchase</field>
</record>
<record model="res.group" id="group_purchase_admin">
<field name="name">Purchase Administrator</field>
<field name="parent" ref="group_purchase"/>
</record>
<record model="res.user-res.group" id="user_admin_group_purchase">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_purchase"/>
</record>
<record model="res.user-res.group" id="user_admin_group_purchase_admin">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_purchase_admin"/>
</record>
<record model="ir.ui.icon" id="purchase_icon">
<field name="name">tryton-purchase</field>
<field name="path">icons/tryton-purchase.svg</field>
</record>
<menuitem
name="Purchases"
sequence="70"
id="menu_purchase"
icon="tryton-purchase"/>
<record model="ir.ui.menu-res.group" id="menu_purchase_group_purchase">
<field name="menu" ref="menu_purchase"/>
<field name="group" ref="group_purchase"/>
</record>
<menuitem
name="Reporting"
parent="menu_purchase"
sequence="100"
id="menu_reporting"
active="True"/>
<record model="ir.action.wizard" id="wizard_shipment_handle_exception">
<field name="name">Handle Shipment Exception</field>
<field name="wiz_name">purchase.handle.shipment.exception</field>
<field name="model">purchase.purchase</field>
</record>
<record model="ir.action.wizard" id="wizard_invoice_handle_exception">
<field name="name">Handle Invoice Exception</field>
<field name="wiz_name">purchase.handle.invoice.exception</field>
<field name="model">purchase.purchase</field>
</record>
<record model="ir.ui.view" id="purchase_view_form">
<field name="model">purchase.purchase</field>
<field name="type">form</field>
<field name="name">purchase_form</field>
</record>
<record model="ir.ui.view" id="purchase_view_tree">
<field name="model">purchase.purchase</field>
<field name="type">tree</field>
<field name="name">purchase_tree</field>
</record>
<record model="ir.ui.view" id="handle_shipment_exception_ask_view_form">
<field name="model">purchase.handle.shipment.exception.ask</field>
<field name="type">form</field>
<field name="name">handle_shipment_exception_ask_form</field>
</record>
<record model="ir.ui.view" id="handle_invoice_exception_ask_view_form">
<field name="model">purchase.handle.invoice.exception.ask</field>
<field name="type">form</field>
<field name="name">handle_invoice_exception_ask_form</field>
</record>
<record model="ir.action.act_window" id="act_purchase_form">
<field name="name">Purchases</field>
<field name="res_model">purchase.purchase</field>
<field name="search_value"></field>
</record>
<record model="ir.action.act_window.view" id="act_purchase_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="purchase_view_tree"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.view" id="act_purchase_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="purchase_view_form"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_draft">
<field name="name">Draft</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('state', '=', 'draft')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_quotation">
<field name="name">Quotation</field>
<field name="sequence" eval="20"/>
<field name="domain" eval="[('state', '=', 'quotation')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_confirmed">
<field name="name">Confirmed</field>
<field name="sequence" eval="30"/>
<field name="domain" eval="[('state', '=', 'confirmed')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_processing">
<field name="name">Processing</field>
<field name="sequence" eval="40"/>
<field name="domain" eval="[('state', '=', 'processing')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_exception">
<field name="name">Exception</field>
<field name="sequence" eval="50"/>
<field name="domain"
eval="['OR', ('invoice_state', '=', 'exception'), ('shipment_state', '=', 'exception')]"
pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_all">
<field name="name">All</field>
<field name="sequence" eval="9999"/>
<field name="domain"></field>
<field name="act_window" ref="act_purchase_form"/>
</record>
<menuitem
parent="menu_purchase"
action="act_purchase_form"
sequence="10"
id="menu_purchase_form"/>
<record model="ir.action.act_window" id="act_purchase_invoice_relate">
<field name="name">Purchases</field>
<field name="res_model">purchase.purchase</field>
<field name="domain"
eval="[('invoices', 'in', Eval('active_ids'))]"
pyson="1"/>
</record>
<record model="ir.action.act_window.view"
id="act_purchase_invoice_relate_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="purchase_view_tree"/>
<field name="act_window" ref="act_purchase_invoice_relate"/>
</record>
<record model="ir.action.act_window.view"
id="act_purchase_invoice_relate_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="purchase_view_form"/>
<field name="act_window" ref="act_purchase_invoice_relate"/>
</record>
<record model="ir.action.keyword"
id="act_purchase_invoice_relate_keyword">
<field name="keyword">form_relate</field>
<field name="model">account.invoice,-1</field>
<field name="action" ref="act_purchase_invoice_relate"/>
</record>
<record model="ir.action.act_window" id="act_purchase_relate">
<field name="name">Purchases</field>
<field name="res_model">purchase.purchase</field>
<field name="domain"
eval="[
If(Eval('active_model') == 'party.party',
('party', 'in', Eval('active_ids', [])), ()),
]"
pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_purchase_relate_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="purchase_view_tree"/>
<field name="act_window" ref="act_purchase_relate"/>
</record>
<record model="ir.action.act_window.view" id="act_purchase_relate_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="purchase_view_form"/>
<field name="act_window" ref="act_purchase_relate"/>
</record>
<record model="ir.action.act_window.domain" id="act_purchase_relate_pending">
<field name="name">Pending</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('state', 'not in', ['done', 'cancelled'])]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_relate"/>
</record>
<record model="ir.action.act_window.domain" id="act_purchase_relate_done">
<field name="name">Done</field>
<field name="sequence" eval="20"/>
<field name="domain" eval="[('state', '=', 'done')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_relate"/>
</record>
<record model="ir.action.act_window.domain" id="act_purchase_relate_all">
<field name="name">All</field>
<field name="sequence" eval="9999"/>
<field name="domain"></field>
<field name="act_window" ref="act_purchase_relate"/>
</record>
<record model="ir.action.keyword" id="act_purchase_relate_keyword_party">
<field name="keyword">form_relate</field>
<field name="model">party.party,-1</field>
<field name="action" ref="act_purchase_relate"/>
</record>
<record model="ir.action.act_window" id="act_purchase_relate_simple">
<field name="name">Purchases</field>
<field name="res_model">purchase.purchase</field>
<field
name="domain"
eval="[
If(Eval('active_model') == 'stock.shipment.in',
('shipments', 'in', Eval('active_ids', [])), ()),
If(Eval('active_model') == 'stock.shipment.in.return',
('shipment_returns', 'in', Eval('active_ids', [])), ()),
]"
pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_purchase_relate_simple_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="purchase_view_tree"/>
<field name="act_window" ref="act_purchase_relate_simple"/>
</record>
<record model="ir.action.act_window.view" id="act_purchase_relate_simple_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="purchase_view_form"/>
<field name="act_window" ref="act_purchase_relate_simple"/>
</record>
<record model="ir.action.keyword" id="act_purchase_relate_simple_keyword_shipment_in">
<field name="keyword">form_relate</field>
<field name="model">stock.shipment.in,-1</field>
<field name="action" ref="act_purchase_relate_simple"/>
</record>
<record model="ir.action.keyword" id="act_purchase_relate_simple_keyword_shipment_in_return">
<field name="keyword">form_relate</field>
<field name="model">stock.shipment.in.return,-1</field>
<field name="action" ref="act_purchase_relate_simple"/>
</record>
<record model="ir.model.access" id="access_purchase">
<field name="model">purchase.purchase</field>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_purchase_purchase">
<field name="model">purchase.purchase</field>
<field name="group" ref="group_purchase"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_purchase_account">
<field name="model">purchase.purchase</field>
<field name="group" ref="account.group_account"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_purchase_group_stock">
<field name="model">purchase.purchase</field>
<field name="group" ref="stock.group_stock"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.field.access" id="access_purchase_purchase_invoices_ignored">
<field name="model">purchase.purchase</field>
<field name="field">invoices_ignored</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
</record>
<record model="ir.model.field.access" id="access_purchase_purchase_invoices_ignored_purchase_admin">
<field name="model">purchase.purchase</field>
<field name="field">invoices_ignored</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
</record>
<record model="ir.model.button" id="purchase_cancel_button">
<field name="model">purchase.purchase</field>
<field name="name">cancel</field>
<field name="string">Cancel</field>
</record>
<record model="ir.model.button" id="purchase_draft_button">
<field name="model">purchase.purchase</field>
<field name="name">draft</field>
<field name="string">Draft</field>
</record>
<record model="ir.model.button" id="purchase_quote_button">
<field name="model">purchase.purchase</field>
<field name="name">quote</field>
<field name="string">Quote</field>
</record>
<record model="ir.model.button" id="purchase_confirm_button">
<field name="model">purchase.purchase</field>
<field name="name">confirm</field>
<field name="string">Confirm</field>
</record>
<record model="ir.model.button" id="purchase_process_button">
<field name="model">purchase.purchase</field>
<field name="name">process</field>
<field name="string">Process</field>
</record>
<record model="ir.model.button-res.group" id="purchase_process_button_group_purchase_admin">
<field name="button" ref="purchase_process_button"/>
<field name="group" ref="group_purchase_admin"/>
</record>
<record model="ir.model.button" id="purchase_manual_invoice_button">
<field name="model">purchase.purchase</field>
<field name="name">manual_invoice</field>
<field name="string">Create Invoice</field>
</record>
<record model="ir.model.button-res.group" id="purchase_manual_button_group_purchase">
<field name="button" ref="purchase_manual_invoice_button"/>
<field name="group" ref="group_purchase"/>
</record>
<record model="ir.model.button-res.group" id="purchase_manual_button_group_account">
<field name="button" ref="purchase_manual_invoice_button"/>
<field name="group" ref="account.group_account"/>
</record>
<record model="ir.model.button" id="purchase_modify_header_button">
<field name="model">purchase.purchase</field>
<field name="name">modify_header</field>
<field name="string">Modify Header</field>
</record>
<record model="ir.model.button"
id="purchase_hande_invoice_exception_button">
<field name="model">purchase.purchase</field>
<field name="name">handle_invoice_exception</field>
<field name="string">Handle Invoice Exception</field>
</record>
<record model="ir.model.button"
id="purchase_hande_shipment_exception_button">
<field name="model">purchase.purchase</field>
<field name="name">handle_shipment_exception</field>
<field name="string">Handle Shipment Exception</field>
</record>
<record model="ir.sequence.type" id="sequence_type_purchase">
<field name="name">Purchase</field>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_purchase_group_admin">
<field name="sequence_type" ref="sequence_type_purchase"/>
<field name="group" ref="res.group_admin"/>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_purchase_group_purchase_admin">
<field name="sequence_type" ref="sequence_type_purchase"/>
<field name="group" ref="group_purchase_admin"/>
</record>
<record model="ir.sequence" id="sequence_purchase">
<field name="name">Purchase</field>
<field name="sequence_type" ref="sequence_type_purchase"/>
</record>
<record model="ir.action.report" id="report_purchase">
<field name="name">Purchase</field>
<field name="model">purchase.purchase</field>
<field name="report_name">purchase.purchase</field>
<field name="report">purchase/purchase.fodt</field>
</record>
<record model="ir.action.keyword" id="report_purchase_keyword">
<field name="keyword">form_print</field>
<field name="model">purchase.purchase,-1</field>
<field name="action" ref="report_purchase"/>
</record>
<record model="ir.ui.view" id="purchase_line_view_form">
<field name="model">purchase.line</field>
<field name="type">form</field>
<field name="name">purchase_line_form</field>
</record>
<record model="ir.ui.view" id="purchase_line_view_tree">
<field name="model">purchase.line</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">purchase_line_tree</field>
</record>
<record model="ir.ui.view" id="purchase_line_view_tree_sequence">
<field name="model">purchase.line</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">purchase_line_tree_sequence</field>
</record>
<record model="ir.action.act_window" id="act_purchase_line_relate">
<field name="name">Purchase Lines</field>
<field name="res_model">purchase.line</field>
<field
name="domain"
eval="[('type', '=', 'line'),
If(Eval('active_model') == 'purchase.purchase',
('purchase', 'in', Eval('active_ids', [])), ()),
If(Eval('active_model') == 'product.product',
('product', 'in', Eval('active_ids', [])), ()),
If(Eval('active_model') == 'product.template',
('product.template.id', 'in', Eval('active_ids', [])), ()),
If(Eval('active_model') == 'party.party',
('supplier', 'in', Eval('active_ids', [])), ()),
]"
pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_purchase_line_relate_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="purchase_line_view_tree"/>
<field name="act_window" ref="act_purchase_line_relate"/>
</record>
<record model="ir.action.act_window.view" id="act_purchase_line_relate_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="purchase_line_view_form"/>
<field name="act_window" ref="act_purchase_line_relate"/>
</record>
<record model="ir.action.act_window.domain" id="act_purchase_line_relate_pending">
<field name="name">Pending</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('purchase_state', 'not in', ['done', 'cancelled'])]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_line_relate"/>
</record>
<record model="ir.action.act_window.domain" id="act_purchase_line_relate_done">
<field name="name">Done</field>
<field name="sequence" eval="20"/>
<field name="domain" eval="[('purchase_state', '=', 'done')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_line_relate"/>
</record>
<record model="ir.action.act_window.domain" id="act_purchase_line_relate_all">
<field name="name">All</field>
<field name="sequence" eval="9999"/>
<field name="domain"></field>
<field name="act_window" ref="act_purchase_line_relate"/>
</record>
<record model="ir.action.keyword" id="act_purchase_line_relate_keyword_purchase">
<field name="keyword">form_relate</field>
<field name="model">purchase.purchase,-1</field>
<field name="action" ref="act_purchase_line_relate"/>
</record>
<record model="ir.action.keyword" id="act_purchase_line_relate_keyword_product">
<field name="keyword">form_relate</field>
<field name="model">product.product,-1</field>
<field name="action" ref="act_purchase_line_relate"/>
</record>
<record model="ir.action.keyword" id="act_purchase_line_relate_keyword_product_template">
<field name="keyword">form_relate</field>
<field name="model">product.template,-1</field>
<field name="action" ref="act_purchase_line_relate"/>
</record>
<record model="ir.action.keyword" id="act_purchase_line_relate_keyword_party">
<field name="keyword">form_relate</field>
<field name="model">party.party,-1</field>
<field name="action" ref="act_purchase_line_relate"/>
</record>
<record model="ir.model.access" id="access_purchase_line_group_stock">
<field name="model">purchase.line</field>
<field name="group" ref="stock.group_stock"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.field.access" id="access_purchase_line_moves_ignored">
<field name="model">purchase.line</field>
<field name="field">moves_ignored</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
</record>
<record model="ir.model.field.access" id="access_purchase_line_moves_ignored_purchase_admin">
<field name="model">purchase.line</field>
<field name="field">moves_ignored</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
</record>
<record model="ir.ui.view" id="product_supplier_view_form">
<field name="model">purchase.product_supplier</field>
<field name="type">form</field>
<field name="name">product_supplier_form</field>
</record>
<record model="ir.ui.view" id="product_supplier_view_tree">
<field name="model">purchase.product_supplier</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">product_supplier_tree</field>
</record>
<record model="ir.ui.view" id="product_supplier_view_tree_sequence">
<field name="model">purchase.product_supplier</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">product_supplier_tree_sequence</field>
</record>
<record model="ir.action.act_window" id="act_product_supplier_form">
<field name="name">Suppliers</field>
<field name="res_model">purchase.product_supplier</field>
</record>
<record model="ir.action.act_window.view"
id="act_product_supplier_list_view">
<field name="sequence" eval="10"/>
<field name="view" ref="product_supplier_view_tree"/>
<field name="act_window" ref="act_product_supplier_form"/>
</record>
<record model="ir.action.act_window.view"
id="act_product_supplier_form_view">
<field name="sequence" eval="20"/>
<field name="view" ref="product_supplier_view_form"/>
<field name="act_window" ref="act_product_supplier_form"/>
</record>
<menuitem
parent="product.menu_template"
sequence="20"
action="act_product_supplier_form"
id="menu_product_supplier"/>
<record model="ir.model.access" id="access_product_supplier">
<field name="model">purchase.product_supplier</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_product_supplier_purchase">
<field name="model">purchase.product_supplier</field>
<field name="group" ref="group_purchase"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.rule.group" id="rule_group_product_supplier_companies">
<field name="name">User in companies</field>
<field name="model">purchase.product_supplier</field>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_product_supplier_companies">
<field name="domain"
eval="[('company', 'in', Eval('companies', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_product_supplier_companies"/>
</record>
<record model="ir.ui.view" id="product_supplier_price_view_form">
<field name="model">purchase.product_supplier.price</field>
<field name="type">form</field>
<field name="name">product_supplier_price_form</field>
</record>
<record model="ir.ui.view" id="product_supplier_price_view_tree">
<field name="model">purchase.product_supplier.price</field>
<field name="type">tree</field>
<field name="name">product_supplier_price_tree</field>
</record>
<record model="ir.ui.view" id="product_supplier_price_view_list_sequence">
<field name="model">purchase.product_supplier.price</field>
<field name="type">tree</field>
<field name="name">product_supplier_price_list_sequence</field>
</record>
<record model="ir.action.act_window"
id="act_product_supplier_price_form">
<field name="name">Prices</field>
<field name="res_model">purchase.product_supplier.price</field>
<field name="domain"
eval="[If(Eval('active_ids', []) == [Eval('active_id')], ('product_supplier', '=', Eval('active_id')), ('product_supplier', 'in', Eval('active_ids')))]"
pyson="1"/>
</record>
<record model="ir.action.keyword"
id="act_product_supplier_price_form_keyword1">
<field name="keyword">form_relate</field>
<field name="model">purchase.product_supplier,-1</field>
<field name="action" ref="act_product_supplier_price_form"/>
</record>
<record model="ir.ui.view" id="return_purchase_start_view_form">
<field name="model">purchase.return_purchase.start</field>
<field name="type">form</field>
<field name="name">return_purchase_start_form</field>
</record>
<record model="ir.action.wizard" id="wizard_return_purchase">
<field name="name">Return Purchase</field>
<field name="wiz_name">purchase.return_purchase</field>
<field name="model">purchase.purchase</field>
</record>
<record model="ir.action.keyword" id="act_wizard_return_purchase_keyword">
<field name="keyword">form_action</field>
<field name="model">purchase.purchase,-1</field>
<field name="action" ref="wizard_return_purchase"/>
</record>
<record model="ir.action.wizard" id="wizard_modify_header">
<field name="name">Modify Header</field>
<field name="wiz_name">purchase.modify_header</field>
<field name="model">purchase.purchase</field>
</record>
<record model="ir.ui.view" id="modify_header_form">
<field name="model">purchase.purchase</field>
<field name="inherit" ref="purchase.purchase_view_form"/>
<field name="name">modify_header_form</field>
<field name="domain"
eval="Eval('context', {}).get('modify_header', False)"
pyson="1"/>
</record>
<record model="ir.rule.group" id="rule_group_purchase_companies">
<field name="name">User in companies</field>
<field name="model">purchase.purchase</field>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_purchase_companies">
<field name="domain"
eval="[('company', 'in', Eval('companies', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_purchase_companies"/>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,390 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from dateutil.relativedelta import relativedelta
from sql import Null
from sql.aggregate import Count, Min, Sum
from sql.conditionals import Coalesce
from sql.functions import DateTrunc
from trytond.i18n import lazy_gettext
from trytond.model import ModelSQL, ModelView, fields
from trytond.modules.currency.fields import Monetary
from trytond.pool import Pool
from trytond.pyson import Eval, If
from trytond.tools import pairwise_longest, sql_pairing, unpair
from trytond.tools.chart import sparkline
from trytond.transaction import Transaction
class Abstract(ModelSQL):
company = fields.Many2One(
'company.company',
lazy_gettext("purchase.msg_purchase_reporting_company"))
number = fields.Integer(
lazy_gettext("purchase.msg_purchase_reporting_number"),
help=lazy_gettext("purchase.msg_purchase_reporting_number_help"))
expense = Monetary(
lazy_gettext("purchase.msg_purchase_reporting_expense"),
digits='currency', currency='currency')
expense_trend = fields.Function(
fields.Char(
lazy_gettext("purchase.msg_purchase_reporting_expense_trend")),
'get_trend')
time_series = None
currency = fields.Many2One(
'currency.currency',
lazy_gettext("purchase.msg_purchase_reporting_currency"))
@classmethod
def table_query(cls):
from_item, tables = cls._joins()
return from_item.select(*cls._columns(tables),
where=cls._where(tables),
group_by=cls._group_by(tables))
@classmethod
def _joins(cls):
pool = Pool()
Line = pool.get('purchase.line')
Purchase = pool.get('purchase.purchase')
tables = {}
tables['line'] = line = Line.__table__()
tables['line.purchase'] = purchase = Purchase.__table__()
from_item = (line
.join(purchase, condition=line.purchase == purchase.id))
return from_item, tables
@classmethod
def _columns(cls, tables):
line = tables['line']
purchase = tables['line.purchase']
quantity = Coalesce(line.actual_quantity, line.quantity)
expense = cls.expense.sql_cast(
Sum(quantity * line.unit_price))
return [
cls._column_id(tables).as_('id'),
purchase.company.as_('company'),
purchase.currency.as_('currency'),
expense.as_('expense'),
Count(purchase.id, distinct=True).as_('number'),
]
@classmethod
def _column_id(cls, tables):
line = tables['line']
return Min(line.id)
@classmethod
def _group_by(cls, tables):
purchase = tables['line.purchase']
return [purchase.company, purchase.currency]
@classmethod
def _where(cls, tables):
context = Transaction().context
purchase = tables['line.purchase']
where = purchase.company == context.get('company')
where &= purchase.state.in_(cls._purchase_states())
from_date = context.get('from_date')
if from_date:
where &= purchase.purchase_date >= from_date
to_date = context.get('to_date')
if to_date:
where &= purchase.purchase_date <= to_date
warehouse = context.get('warehouse')
if warehouse:
where &= purchase.warehouse == warehouse
return where
@classmethod
def _purchase_states(cls):
return ['confirmed', 'processing', 'done']
@property
def time_series_all(self):
delta = self._period_delta()
for ts, next_ts in pairwise_longest(self.time_series or []):
yield ts
if delta and next_ts:
date = ts.date + delta
while date < next_ts.date:
yield None
date += delta
@classmethod
def _period_delta(cls):
context = Transaction().context
return {
'year': relativedelta(years=1),
'month': relativedelta(months=1),
'day': relativedelta(days=1),
}.get(context.get('period'))
def get_trend(self, name):
name = name[:-len('_trend')]
return sparkline([
getattr(ts, name) if ts else 0 for ts in self.time_series_all])
class AbstractTimeseries(Abstract):
date = fields.Date(lazy_gettext('purchase.msg_purchase_reporting_date'))
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('date', 'ASC'))
@classmethod
def _columns(cls, tables):
return super()._columns(tables) + [
cls._column_date(tables).as_('date')]
@classmethod
def _column_date(cls, tables):
context = Transaction().context
purchase = tables['line.purchase']
date = DateTrunc(context.get('period'), purchase.purchase_date)
date = cls.date.sql_cast(date)
return date
@classmethod
def _group_by(cls, tables):
return super()._group_by(tables) + [cls._column_date(tables)]
class Context(ModelView):
__name__ = 'purchase.reporting.context'
company = fields.Many2One('company.company', "Company", required=True)
from_date = fields.Date("From Date",
domain=[
If(Eval('to_date') & Eval('from_date'),
('from_date', '<=', Eval('to_date')),
()),
],
depends=['to_date'])
to_date = fields.Date("To Date",
domain=[
If(Eval('from_date') & Eval('to_date'),
('to_date', '>=', Eval('from_date')),
()),
],
depends=['from_date'])
period = fields.Selection([
('year', "Year"),
('month', "Month"),
('day', "Day"),
], "Period", required=True)
warehouse = fields.Many2One(
'stock.location', "Warehouse",
domain=[
('type', '=', 'warehouse'),
])
@classmethod
def default_company(cls):
return Transaction().context.get('company')
@classmethod
def default_from_date(cls):
pool = Pool()
Date = pool.get('ir.date')
context = Transaction().context
if 'from_date' in context:
return context['from_date']
return Date.today() - relativedelta(years=1)
@classmethod
def default_to_date(cls):
pool = Pool()
Date = pool.get('ir.date')
context = Transaction().context
if 'to_date' in context:
return context['to_date']
return Date.today()
@classmethod
def default_period(cls):
return Transaction().context.get('period', 'month')
@classmethod
def default_warehouse(cls):
return Transaction().context.get('warehouse')
class Main(Abstract, ModelView):
__name__ = 'purchase.reporting.main'
time_series = fields.Function(fields.Many2Many(
'purchase.reporting.main.time_series', None, None,
lazy_gettext('purchase.msg_purchase_reporting_time_series')),
'get_time_series')
def get_rec_name(self, name):
return ''
def get_time_series(self, name):
pool = Pool()
Timeseries = pool.get('purchase.reporting.main.time_series')
return [t.id for t in Timeseries.search([])]
class MainTimeseries(AbstractTimeseries, ModelView):
__name__ = 'purchase.reporting.main.time_series'
class SupplierMixin(object):
__slots__ = ()
supplier = fields.Many2One(
'party.party', "Supplier",
context={
'company': Eval('company', -1),
},
depends=['company'])
@classmethod
def _columns(cls, tables):
purchase = tables['line.purchase']
return super()._columns(tables) + [purchase.party.as_('supplier')]
@classmethod
def _group_by(cls, tables):
purchase = tables['line.purchase']
return super()._group_by(tables) + [purchase.party]
def get_rec_name(self, name):
return self.supplier.rec_name
@classmethod
def search_rec_name(cls, name, clause):
return [('supplier.rec_name', *clause[1:])]
class Supplier(SupplierMixin, Abstract, ModelView):
__name__ = 'purchase.reporting.supplier'
time_series = fields.One2Many(
'purchase.reporting.supplier.time_series', 'supplier',
lazy_gettext('purchase.msg_purchase_reporting_time_series'))
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('supplier', 'ASC'))
@classmethod
def _column_id(cls, tables):
purchase = tables['line.purchase']
return sql_pairing(purchase.party, purchase.currency)
class SupplierTimeseries(SupplierMixin, AbstractTimeseries, ModelView):
__name__ = 'purchase.reporting.supplier.time_series'
supplier_currency = fields.Integer("Supplier - Currency")
@classmethod
def _columns(cls, tables):
purchase = tables['line.purchase']
return super()._columns(tables) + [
sql_pairing(
purchase.party, purchase.currency).as_('supplier_currency'),
]
class ProductMixin(object):
__slots__ = ()
product = fields.Many2One(
'product.product', "Product",
context={
'company': Eval('company', -1),
},
depends=['company'])
product_supplier = fields.Many2One(
'purchase.product_supplier', "Supplier's Product")
@classmethod
def _columns(cls, tables):
line = tables['line']
return super()._columns(tables) + [
line.product.as_('product'),
line.product_supplier.as_('product_supplier'),
]
@classmethod
def _group_by(cls, tables):
line = tables['line']
return super()._group_by(tables) + [
line.product, line.product_supplier]
@classmethod
def _where(cls, tables):
context = Transaction().context
line = tables['line']
purchase = tables['line.purchase']
where = super()._where(tables)
where &= line.product != Null
if context.get('supplier_currency') is not None:
supplier, currency = unpair(context['supplier_currency'])
else:
supplier = context.get('supplier')
currency = context.get('currency')
where &= purchase.party == supplier
where &= purchase.currency == currency
return where
def get_rec_name(self, name):
pool = Pool()
Party = pool.get('party.party')
context = Transaction().context
name = self.product.rec_name if self.product else None
if context.get('supplier'):
supplier = Party(context['supplier'])
name += '@%s' % supplier.rec_name
return name
@classmethod
def search_rec_name(cls, name, clause):
return [('product.rec_name', *clause[1:])]
class Product(ProductMixin, Abstract, ModelView):
__name__ = 'purchase.reporting.product'
time_series = fields.One2Many(
'purchase.reporting.product.time_series', 'product',
lazy_gettext('purchase.msg_purchase_reporting_time_series'))
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('product', 'ASC'))
@classmethod
def _column_id(cls, tables):
line = tables['line']
purchase = tables['line.purchase']
return sql_pairing(line.product, purchase.currency)
class ProductTimeseries(ProductMixin, AbstractTimeseries, ModelView):
__name__ = 'purchase.reporting.product.time_series'
product_currency = fields.Integer("Product - Currency")
@classmethod
def _columns(cls, tables):
line = tables['line']
purchase = tables['line.purchase']
return super()._columns(tables) + [
sql_pairing(
line.product, purchase.currency).as_('product_currency'),
]

View File

@@ -0,0 +1,456 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<menuitem parent="menu_reporting"
name="Purchases"
sequence="10"
id="menu_reporting_purchase"
icon="tryton-graph"/>
<record model="ir.ui.view" id="reporting_context_view_form">
<field name="model">purchase.reporting.context</field>
<field name="type">form</field>
<field name="name">purchase_reporting_context_form</field>
</record>
<record model="ir.ui.view" id="reporting_main_view_list">
<field name="model">purchase.reporting.main</field>
<field name="type">tree</field>
<field name="name">purchase_reporting_main_list</field>
</record>
<record model="ir.ui.view" id="reporting_main_view_graph_expense">
<field name="model">purchase.reporting.main</field>
<field name="type">graph</field>
<field name="name">purchase_reporting_main_graph_expense</field>
</record>
<record model="ir.ui.view" id="reporting_main_view_graph_number">
<field name="model">purchase.reporting.main</field>
<field name="type">graph</field>
<field name="name">purchase_reporting_main_graph_number</field>
</record>
<record model="ir.action.act_window" id="act_reporting_main">
<field name="name">Purchases</field>
<field name="res_model">purchase.reporting.main</field>
<field name="context_model">purchase.reporting.context</field>
</record>
<record model="ir.action.act_window.view" id="act_reporting_main_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="reporting_main_view_list"/>
<field name="act_window" ref="act_reporting_main"/>
</record>
<record model="ir.action.keyword" id="act_reporting_main_keyword1">
<field name="keyword">tree_open</field>
<field name="model" ref="menu_reporting_purchase"/>
<field name="action" ref="act_reporting_main"/>
</record>
<record model="ir.rule.group" id="rule_group_reporting_main_companies">
<field name="name">User in companies</field>
<field name="model">purchase.reporting.main</field>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_reporting_main_companies">
<field name="domain"
eval="[('company', 'in', Eval('companies', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_reporting_main_companies"/>
</record>
<record model="ir.model.access" id="access_reporting_main">
<field name="model">purchase.reporting.main</field>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_reporting_main_purchase">
<field name="model">purchase.reporting.main</field>
<field name="group" ref="group_purchase"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.ui.view" id="reporting_main_time_series_view_list">
<field name="model">purchase.reporting.main.time_series</field>
<field name="type">tree</field>
<field name="name">purchase_reporting_main_time_series_list</field>
</record>
<record model="ir.ui.view" id="reporting_main_time_series_view_graph_expense">
<field name="model">purchase.reporting.main.time_series</field>
<field name="type">graph</field>
<field name="name">purchase_reporting_main_time_series_graph_expense</field>
</record>
<record model="ir.ui.view" id="reporting_main_time_series_view_graph_number">
<field name="model">purchase.reporting.main.time_series</field>
<field name="type">graph</field>
<field name="name">purchase_reporting_main_time_series_graph_number</field>
</record>
<record model="ir.action.act_window" id="act_reporting_main_time_series">
<field name="name">Purchases</field>
<field name="res_model">purchase.reporting.main.time_series</field>
<field name="context_model">purchase.reporting.context</field>
<field name="order" eval="[('date', 'DESC')]" pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_main_time_series_list_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="reporting_main_time_series_view_list"/>
<field name="act_window" ref="act_reporting_main_time_series"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_main_time_series_list_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="reporting_main_time_series_view_graph_expense"/>
<field name="act_window" ref="act_reporting_main_time_series"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_main_time_series_list_view3">
<field name="sequence" eval="30"/>
<field name="view" ref="reporting_main_time_series_view_graph_number"/>
<field name="act_window" ref="act_reporting_main_time_series"/>
</record>
<record model="ir.action.keyword" id="act_reporting_main_time_series_list_keyword1">
<field name="keyword">tree_open</field>
<field name="model">purchase.reporting.main,-1</field>
<field name="action" ref="act_reporting_main_time_series"/>
</record>
<record model="ir.rule.group" id="rule_group_reporting_main_time_series_companies">
<field name="name">User in companies</field>
<field name="model">purchase.reporting.main.time_series</field>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_reporting_main_time_series_companies">
<field name="domain"
eval="[('company', 'in', Eval('companies', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_reporting_main_time_series_companies"/>
</record>
<record model="ir.model.access" id="access_reporting_main_time_series">
<field name="model">purchase.reporting.main.time_series</field>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_reporting_main_time_series_purchase">
<field name="model">purchase.reporting.main.time_series</field>
<field name="group" ref="group_purchase"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<!-- Supplier -->
<record model="ir.ui.view" id="reporting_supplier_view_list">
<field name="model">purchase.reporting.supplier</field>
<field name="inherit" ref="reporting_main_view_list"/>
<field name="name">purchase_reporting_supplier_list</field>
</record>
<record model="ir.ui.view" id="reporting_supplier_view_graph_expense">
<field name="model">purchase.reporting.supplier</field>
<field name="inherit" ref="reporting_main_view_graph_expense"/>
<field name="name">purchase_reporting_supplier_graph_expense</field>
</record>
<record model="ir.ui.view" id="reporting_supplier_view_graph_number">
<field name="model">purchase.reporting.supplier</field>
<field name="inherit" ref="reporting_main_view_graph_number"/>
<field name="name">purchase_reporting_supplier_graph_number</field>
</record>
<record model="ir.action.act_window" id="act_reporting_supplier">
<field name="name">Purchases per Supplier</field>
<field name="res_model">purchase.reporting.supplier</field>
<field name="context_model">purchase.reporting.context</field>
</record>
<record model="ir.action.act_window.view" id="act_reporting_supplier_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="reporting_supplier_view_list"/>
<field name="act_window" ref="act_reporting_supplier"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_supplier_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="reporting_supplier_view_graph_expense"/>
<field name="act_window" ref="act_reporting_supplier"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_supplier_view3">
<field name="sequence" eval="30"/>
<field name="view" ref="reporting_supplier_view_graph_number"/>
<field name="act_window" ref="act_reporting_supplier"/>
</record>
<record model="ir.action.keyword" id="act_reporting_supplier_keyword1">
<field name="keyword">tree_open</field>
<field name="model" ref="menu_reporting_purchase"/>
<field name="action" ref="act_reporting_supplier"/>
</record>
<record model="ir.rule.group" id="rule_group_reporting_supplier_companies">
<field name="name">User in companies</field>
<field name="model">purchase.reporting.supplier</field>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_reporting_supplier_companies">
<field name="domain"
eval="[('company', 'in', Eval('companies', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_reporting_supplier_companies"/>
</record>
<record model="ir.model.access" id="access_reporting_supplier">
<field name="model">purchase.reporting.supplier</field>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_reporting_supplier_purchase">
<field name="model">purchase.reporting.supplier</field>
<field name="group" ref="group_purchase"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.ui.view" id="reporting_supplier_time_series_view_list">
<field name="model">purchase.reporting.supplier.time_series</field>
<field name="type">tree</field>
<field name="name">purchase_reporting_main_time_series_list</field>
</record>
<record model="ir.ui.view" id="reporting_supplier_time_series_view_graph_expense">
<field name="model">purchase.reporting.supplier.time_series</field>
<field name="type">graph</field>
<field name="name">purchase_reporting_main_time_series_graph_expense</field>
</record>
<record model="ir.ui.view" id="reporting_supplier_time_series_view_graph_number">
<field name="model">purchase.reporting.supplier.time_series</field>
<field name="type">graph</field>
<field name="name">purchase_reporting_main_time_series_graph_number</field>
</record>
<record model="ir.action.act_window" id="act_reporting_supplier_time_series">
<field name="name">Purchases per Supplier</field>
<field name="res_model">purchase.reporting.supplier.time_series</field>
<field name="context_model">purchase.reporting.context</field>
<field
name="domain"
eval="[('supplier_currency', '=', Eval('active_id', -1))]"
pyson="1"/>
<field name="order" eval="[('date', 'DESC')]" pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_supplier_time_series_list_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="reporting_supplier_time_series_view_list"/>
<field name="act_window" ref="act_reporting_supplier_time_series"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_supplier_time_series_list_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="reporting_supplier_time_series_view_graph_expense"/>
<field name="act_window" ref="act_reporting_supplier_time_series"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_supplier_time_series_list_view3">
<field name="sequence" eval="30"/>
<field name="view" ref="reporting_supplier_time_series_view_graph_number"/>
<field name="act_window" ref="act_reporting_supplier_time_series"/>
</record>
<record model="ir.action.keyword" id="act_reporting_supplier_time_series_list_keyword1">
<field name="keyword">tree_open</field>
<field name="model">purchase.reporting.supplier,-1</field>
<field name="action" ref="act_reporting_supplier_time_series"/>
</record>
<record model="ir.rule.group" id="rule_group_reporting_supplier_time_series_companies">
<field name="name">User in companies</field>
<field name="model">purchase.reporting.supplier.time_series</field>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_reporting_supplier_time_series_companies">
<field name="domain"
eval="[('company', 'in', Eval('companies', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_reporting_supplier_time_series_companies"/>
</record>
<record model="ir.model.access" id="access_reporting_supplier_time_series">
<field name="model">purchase.reporting.supplier.time_series</field>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_reporting_supplier_time_series_purchase">
<field name="model">purchase.reporting.supplier.time_series</field>
<field name="group" ref="group_purchase"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<!-- Product -->
<record model="ir.ui.view" id="reporting_product_view_list">
<field name="model">purchase.reporting.product</field>
<field name="inherit" ref="reporting_main_view_list"/>
<field name="name">purchase_reporting_product_list</field>
</record>
<record model="ir.ui.view" id="reporting_product_view_graph_expense">
<field name="model">purchase.reporting.product</field>
<field name="inherit" ref="reporting_main_view_graph_expense"/>
<field name="name">purchase_reporting_product_graph_expense</field>
</record>
<record model="ir.ui.view" id="reporting_product_view_graph_number">
<field name="model">purchase.reporting.product</field>
<field name="inherit" ref="reporting_main_view_graph_number"/>
<field name="name">purchase_reporting_product_graph_number</field>
</record>
<record model="ir.action.act_window" id="act_reporting_product">
<field name="name">Purchases per Product</field>
<field name="res_model">purchase.reporting.product</field>
<field name="context_model">purchase.reporting.context</field>
<field name="context" eval="{'supplier_currency': Eval('active_id')}" pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_product_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="reporting_product_view_list"/>
<field name="act_window" ref="act_reporting_product"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_product_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="reporting_product_view_graph_expense"/>
<field name="act_window" ref="act_reporting_product"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_product_view3">
<field name="sequence" eval="30"/>
<field name="view" ref="reporting_product_view_graph_number"/>
<field name="act_window" ref="act_reporting_product"/>
</record>
<record model="ir.action.keyword" id="act_reporting_product_keyword1">
<field name="keyword">tree_open</field>
<field name="model">purchase.reporting.supplier,-1</field>
<field name="action" ref="act_reporting_product"/>
</record>
<record model="ir.rule.group" id="rule_group_reporting_product_companies">
<field name="name">User in companies</field>
<field name="model">purchase.reporting.product</field>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_reporting_product_companies">
<field name="domain"
eval="[('company', 'in', Eval('companies', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_reporting_product_companies"/>
</record>
<record model="ir.model.access" id="access_reporting_product">
<field name="model">purchase.reporting.product</field>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_reporting_product_purchase">
<field name="model">purchase.reporting.product</field>
<field name="group" ref="group_purchase"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.ui.view" id="reporting_product_time_series_view_list">
<field name="model">purchase.reporting.product.time_series</field>
<field name="type">tree</field>
<field name="name">purchase_reporting_main_time_series_list</field>
</record>
<record model="ir.ui.view" id="reporting_product_time_series_view_graph_expense">
<field name="model">purchase.reporting.product.time_series</field>
<field name="type">graph</field>
<field name="name">purchase_reporting_main_time_series_graph_expense</field>
</record>
<record model="ir.ui.view" id="reporting_product_time_series_view_graph_number">
<field name="model">purchase.reporting.product.time_series</field>
<field name="type">graph</field>
<field name="name">purchase_reporting_main_time_series_graph_number</field>
</record>
<record model="ir.action.act_window" id="act_reporting_product_time_series">
<field name="name">Purchases per Product</field>
<field name="res_model">purchase.reporting.product.time_series</field>
<field name="context_model">purchase.reporting.context</field>
<field name="domain"
eval="[('product_currency', '=', Eval('active_id', -1))]"
pyson="1"/>
<field name="order" eval="[('date', 'DESC')]" pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_product_time_series_list_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="reporting_product_time_series_view_list"/>
<field name="act_window" ref="act_reporting_product_time_series"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_product_time_series_list_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="reporting_product_time_series_view_graph_expense"/>
<field name="act_window" ref="act_reporting_product_time_series"/>
</record>
<record model="ir.action.act_window.view" id="act_reporting_product_time_series_list_view3">
<field name="sequence" eval="30"/>
<field name="view" ref="reporting_product_time_series_view_graph_number"/>
<field name="act_window" ref="act_reporting_product_time_series"/>
</record>
<record model="ir.action.keyword" id="act_reporting_product_time_series_list_keyword1">
<field name="keyword">tree_open</field>
<field name="model">purchase.reporting.product,-1</field>
<field name="action" ref="act_reporting_product_time_series"/>
</record>
<record model="ir.rule.group" id="rule_group_reporting_product_time_series_companies">
<field name="name">User in companies</field>
<field name="model">purchase.reporting.product.time_series</field>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_reporting_product_time_series_companies">
<field name="domain"
eval="[('company', 'in', Eval('companies', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_reporting_product_time_series_companies"/>
</record>
<record model="ir.model.access" id="access_reporting_product_time_series">
<field name="model">purchase.reporting.product.time_series</field>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_reporting_product_time_series_purchase">
<field name="model">purchase.reporting.product.time_series</field>
<field name="group" ref="group_purchase"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
</data>
</tryton>

270
modules/purchase/stock.py Normal file
View File

@@ -0,0 +1,270 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from functools import wraps
from trytond.i18n import gettext
from trytond.model import ModelView, Workflow, fields
from trytond.model.exceptions import AccessError
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.tools import cached_property
from trytond.transaction import Transaction, without_check_access
def process_purchase(moves_field):
def _process_purchase(func):
@wraps(func)
def wrapper(cls, shipments):
pool = Pool()
Purchase = pool.get('purchase.purchase')
transaction = Transaction()
context = transaction.context
with without_check_access():
purchases = set(m.purchase for s in cls.browse(shipments)
for m in getattr(s, moves_field) if m.purchase)
result = func(cls, shipments)
if purchases:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Purchase.__queue__.process(purchases)
return result
return wrapper
return _process_purchase
class ShipmentIn(metaclass=PoolMeta):
__name__ = 'stock.shipment.in'
@classmethod
def __setup__(cls):
super().__setup__()
add_remove = [
('supplier', '=', Eval('supplier')),
]
if not cls.incoming_moves.add_remove:
cls.incoming_moves.add_remove = add_remove
else:
cls.incoming_moves.add_remove = [
add_remove,
cls.incoming_moves.add_remove,
]
cls.incoming_moves.depends.add('supplier')
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, shipments):
PurchaseLine = Pool().get('purchase.line')
for shipment in shipments:
for move in shipment.incoming_moves:
if (move.state == 'cancelled'
and isinstance(move.origin, PurchaseLine)):
raise AccessError(
gettext('purchase.msg_purchase_move_reset_draft',
move=move.rec_name))
return super().draft(shipments)
@classmethod
@ModelView.button
@Workflow.transition('received')
@process_purchase('incoming_moves')
def receive(cls, shipments):
pool = Pool()
PurchaseLine = pool.get('purchase.line')
for shipment in shipments:
for move in shipment.incoming_moves:
if isinstance(move.origin, PurchaseLine):
move.origin.check_move_quantity()
super().receive(shipments)
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
@process_purchase('incoming_moves')
def cancel(cls, shipments):
super().cancel(shipments)
class ShipmentInReturn(metaclass=PoolMeta):
__name__ = 'stock.shipment.in.return'
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, shipments):
PurchaseLine = Pool().get('purchase.line')
for shipment in shipments:
for move in shipment.moves:
if (move.state == 'cancelled'
and isinstance(move.origin, PurchaseLine)):
raise AccessError(
gettext('purchase.msg_purchase_move_reset_draft',
move=move.rec_name))
return super().draft(shipments)
@classmethod
@ModelView.button
@Workflow.transition('done')
@process_purchase('moves')
def do(cls, shipments):
super().do(shipments)
def process_purchase_move(without_shipment=False):
def _process_purchase_move(func):
@wraps(func)
def wrapper(cls, moves):
pool = Pool()
Purchase = pool.get('purchase.purchase')
transaction = Transaction()
context = transaction.context
with without_check_access():
p_moves = cls.browse(moves)
if without_shipment:
p_moves = [m for m in p_moves if not m.shipment]
purchases = set(m.purchase for m in p_moves if m.purchase)
if purchases:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Purchase.__queue__.process(purchases)
return func(cls, moves)
return wrapper
return _process_purchase_move
class Move(metaclass=PoolMeta):
__name__ = 'stock.move'
purchase = fields.Function(
fields.Many2One('purchase.purchase', 'Purchase'),
'get_purchase', searcher='search_purchase')
supplier = fields.Function(fields.Many2One(
'party.party', 'Supplier',
context={
'company': Eval('company', -1),
},
depends={'company'}),
'get_supplier', searcher='search_supplier')
purchase_exception_state = fields.Function(fields.Selection([
('', ''),
('ignored', 'Ignored'),
('recreated', 'Recreated'),
], 'Exception State'), 'get_purchase_exception_state')
@classmethod
def __setup__(cls):
super().__setup__()
if not cls.origin.domain:
cls.origin.domain = {}
cls.origin.domain['purchase.line'] = [
('type', '=', 'line'),
]
@classmethod
def _get_origin(cls):
models = super()._get_origin()
models.append('purchase.line')
return models
@classmethod
def check_origin_types(cls):
types = super().check_origin_types()
types.add('supplier')
return types
def get_purchase(self, name):
PurchaseLine = Pool().get('purchase.line')
if isinstance(self.origin, PurchaseLine):
return self.origin.purchase.id
@classmethod
def search_purchase(cls, name, clause):
return [('origin.' + clause[0],) + tuple(clause[1:3])
+ ('purchase.line',) + tuple(clause[3:])]
def get_purchase_exception_state(self, name):
PurchaseLine = Pool().get('purchase.line')
if not isinstance(self.origin, PurchaseLine):
return ''
if self in self.origin.moves_recreated:
return 'recreated'
if self in self.origin.moves_ignored:
return 'ignored'
def get_supplier(self, name):
PurchaseLine = Pool().get('purchase.line')
if isinstance(self.origin, PurchaseLine):
return self.origin.purchase.party.id
@cached_property
def product_name(self):
pool = Pool()
PurchaseLine = pool.get('purchase.line')
name = super().product_name
if (isinstance(self.origin, PurchaseLine)
and self.origin.product_supplier):
name = self.origin.product_supplier.rec_name
return name
@fields.depends('origin')
def on_change_with_product_uom_category(self, name=None):
pool = Pool()
PurchaseLine = pool.get('purchase.line')
category = super().on_change_with_product_uom_category(
name=name)
# Enforce the same unit category as they are used to compute the
# remaining quantity to receive and the quantity to invoice.
# Use getattr as reference field can have negative id
if (isinstance(self.origin, PurchaseLine)
and getattr(self.origin, 'unit', None)):
category = self.origin.unit.category
return category
@property
def origin_name(self):
pool = Pool()
PurchaseLine = pool.get('purchase.line')
name = super().origin_name
if isinstance(self.origin, PurchaseLine) and self.origin.id >= 0:
name = self.origin.purchase.rec_name
return name
@classmethod
def search_supplier(cls, name, clause):
return [('origin.purchase.party' + clause[0][len(name):],
*clause[1:3], 'purchase.line', *clause[3:])]
@classmethod
@ModelView.button
@Workflow.transition('done')
@process_purchase_move(without_shipment=True)
def do(cls, moves):
super().do(moves)
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
@process_purchase_move(without_shipment=True)
def cancel(cls, moves):
super().cancel(moves)
@classmethod
@process_purchase_move()
def on_delete(cls, moves):
return super().on_delete(moves)
class Location(metaclass=PoolMeta):
__name__ = 'stock.location'
supplier_return_location = fields.Many2One(
'stock.location', 'Supplier Return',
states={
'invisible': Eval('type') != 'warehouse',
},
domain=[
('type', '=', 'storage'),
('parent', 'child_of', [Eval('id', -1)]),
],
help='If empty the Storage location is used.')

114
modules/purchase/stock.xml Normal file
View File

@@ -0,0 +1,114 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="move_view_list_shipment">
<field name="model">stock.move</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">move_list_shipment</field>
</record>
<record model="ir.model.access" id="access_move_group_purchase">
<field name="model">stock.move</field>
<field name="group" ref="group_purchase"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.ui.view" id="location_view_form">
<field name="model">stock.location</field>
<field name="inherit" ref="stock.location_view_form"/>
<field name="name">location_form</field>
</record>
<record model="ir.ui.view" id="move_view_list_shipment_in">
<field name="model">stock.move</field>
<field name="inherit" ref="stock.move_view_list_shipment_in"/>
<field name="name">move_list_shipment_in</field>
</record>
<record model="ir.action.act_window" id="act_purchase_move_relate">
<field name="name">Stock Moves</field>
<field name="res_model">stock.move</field>
<field
name="domain"
eval="[
If(Eval('active_model') == 'purchase.purchase',
('purchase', 'in', Eval('active_ids', [])), ()),
If(Eval('active_model') == 'purchase.line',
('origin.id', 'in', Eval('active_ids', []), 'purchase.line'), ()),
]"
pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_move_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="move_view_list_shipment"/>
<field name="act_window" ref="act_purchase_move_relate"/>
</record>
<record model="ir.action.act_window.view" id="act_move_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="stock.move_view_form"/>
<field name="act_window" ref="act_purchase_move_relate"/>
</record>
<record model="ir.action.keyword" id="act_move_form_keyword_purchase">
<field name="keyword">form_relate</field>
<field name="model">purchase.purchase,-1</field>
<field name="action" ref="act_purchase_move_relate"/>
</record>
<record model="ir.action.keyword" id="act_move_form_keyword_purchase_line">
<field name="keyword">form_relate</field>
<field name="model">purchase.line,-1</field>
<field name="action" ref="act_purchase_move_relate"/>
</record>
<record model="ir.action.act_window" id="act_shipment_form">
<field name="name">Shipments</field>
<field name="res_model">stock.shipment.in</field>
<field name="domain"
eval="[('moves.purchase', 'in', Eval('active_ids'))]"
pyson="1"/>
</record>
<record model="ir.action.keyword"
id="act_open_shipment_keyword1">
<field name="keyword">form_relate</field>
<field name="model">purchase.purchase,-1</field>
<field name="action" ref="act_shipment_form"/>
</record>
<record model="ir.model.access" id="access_shipment_in_group_purchase">
<field name="model">stock.shipment.in</field>
<field name="group" ref="group_purchase"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.action.act_window" id="act_return_form">
<field name="name">Returns</field>
<field name="res_model">stock.shipment.in.return</field>
<field name="domain"
eval="[('moves.purchase', 'in', Eval('active_ids'))]"
pyson="1"/>
</record>
<record model="ir.action.keyword"
id="act_open_shipment_return_keyword1">
<field name="keyword">form_relate</field>
<field name="model">purchase.purchase,-1</field>
<field name="action" ref="act_return_form"/>
</record>
<record model="ir.model.access" id="access_shipment_in_return_group_purchase">
<field name="model">stock.shipment.in.return</field>
<field name="group" ref="group_purchase"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,2 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

View File

@@ -0,0 +1,621 @@
=================
Purchase Scenario
=================
Imports::
>>> import datetime as dt
>>> from decimal import Decimal
>>> from proteus import Model, Report
>>> from trytond.modules.account.tests.tools import (
... create_chart, create_fiscalyear, create_tax, get_accounts)
>>> from trytond.modules.account_invoice.tests.tools import (
... create_payment_term, set_fiscalyear_invoice_sequences)
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules, assertEqual, set_user
>>> today = dt.date.today()
Activate modules::
>>> config = activate_modules('purchase', create_company, create_chart)
>>> Employee = Model.get('company.employee')
>>> Party = Model.get('party.party')
>>> User = Model.get('res.user')
Set employee::
>>> employee_party = Party(name="Employee")
>>> employee_party.save()
>>> employee = Employee(party=employee_party)
>>> employee.save()
>>> user = User(config.user)
>>> user.employees.append(employee)
>>> user.employee = employee
>>> user.save()
>>> set_user(user.id)
Create fiscal year::
>>> fiscalyear = set_fiscalyear_invoice_sequences(
... create_fiscalyear(today=today))
>>> fiscalyear.click('create_period')
Get accounts::
>>> accounts = get_accounts()
>>> revenue = accounts['revenue']
>>> expense = accounts['expense']
>>> cash = accounts['cash']
>>> Journal = Model.get('account.journal')
>>> PaymentMethod = Model.get('account.invoice.payment.method')
>>> cash_journal, = Journal.find([('type', '=', 'cash')])
>>> cash_journal.save()
>>> payment_method = PaymentMethod()
>>> payment_method.name = 'Cash'
>>> payment_method.journal = cash_journal
>>> payment_method.credit_account = cash
>>> payment_method.debit_account = cash
>>> payment_method.save()
Create tax::
>>> tax = create_tax(Decimal('.10'))
>>> tax.save()
Create parties::
>>> Party = Model.get('party.party')
>>> supplier = Party(name='Supplier')
>>> supplier.customer_code = '1234'
>>> supplier.save()
>>> customer = Party(name='Customer')
>>> customer.save()
Create account categories::
>>> ProductCategory = Model.get('product.category')
>>> account_category = ProductCategory(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_expense = expense
>>> account_category.account_revenue = revenue
>>> account_category.save()
>>> account_category_tax, = account_category.duplicate()
>>> account_category_tax.supplier_taxes.append(tax)
>>> account_category_tax.save()
Create product::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> ProductTemplate = Model.get('product.template')
>>> template = ProductTemplate()
>>> template.name = 'product'
>>> template.default_uom = unit
>>> template.type = 'goods'
>>> template.purchasable = True
>>> template.list_price = Decimal('10')
>>> template.cost_price_method = 'fixed'
>>> template.account_category = account_category_tax
>>> template.save()
>>> product, = template.products
>>> template = ProductTemplate()
>>> template.name = 'service'
>>> template.default_uom = unit
>>> template.type = 'service'
>>> template.purchasable = True
>>> template.list_price = Decimal('10')
>>> template.cost_price_method = 'fixed'
>>> template.account_category = account_category
>>> template.save()
>>> service, = template.products
Create payment term::
>>> payment_term = create_payment_term()
>>> payment_term.save()
Create an Inventory::
>>> Inventory = Model.get('stock.inventory')
>>> Location = Model.get('stock.location')
>>> storage, = Location.find([
... ('code', '=', 'STO'),
... ])
>>> inventory = Inventory()
>>> inventory.location = storage
>>> inventory_line = inventory.lines.new(product=product)
>>> inventory_line.quantity = 100.0
>>> inventory_line.expected_quantity = 0.0
>>> inventory.click('confirm')
>>> inventory.state
'done'
Purchase 5 products::
>>> Purchase = Model.get('purchase.purchase')
>>> PurchaseLine = Model.get('purchase.line')
>>> purchase = Purchase()
>>> purchase.party = supplier
>>> purchase.payment_term = payment_term
>>> purchase.invoice_method = 'order'
>>> purchase_line = PurchaseLine()
>>> purchase.lines.append(purchase_line)
>>> purchase_line.product = product
>>> purchase_line.quantity = 2.0
>>> purchase_line.unit_price = Decimal('5.0000')
>>> purchase_line = PurchaseLine()
>>> purchase.lines.append(purchase_line)
>>> purchase_line.type = 'comment'
>>> purchase_line.description = 'Comment'
>>> purchase_line = PurchaseLine()
>>> purchase.lines.append(purchase_line)
>>> purchase_line.product = product
>>> purchase_line.quantity = 3.0
>>> purchase_line.unit_price = Decimal('5.0000')
>>> purchase.click('quote')
>>> purchase.untaxed_amount, purchase.tax_amount, purchase.total_amount
(Decimal('25.00'), Decimal('2.50'), Decimal('27.50'))
>>> assertEqual(purchase.quoted_by, employee)
>>> purchase.click('confirm')
>>> purchase.untaxed_amount, purchase.tax_amount, purchase.total_amount
(Decimal('25.00'), Decimal('2.50'), Decimal('27.50'))
>>> assertEqual(purchase.confirmed_by, employee)
>>> purchase.state
'processing'
>>> purchase.shipment_state
'waiting'
>>> purchase.invoice_state
'pending'
>>> len(purchase.moves), len(purchase.shipment_returns), len(purchase.invoices)
(2, 0, 1)
>>> invoice, = purchase.invoices
>>> assertEqual(invoice.origins, purchase.rec_name)
>>> invoice.untaxed_amount, invoice.tax_amount, invoice.total_amount
(Decimal('25.00'), Decimal('2.50'), Decimal('27.50'))
Invoice line must be linked to stock move::
>>> invoice_line1, invoice_line2 = sorted(
... invoice.lines, key=lambda l: l.quantity or 0)
>>> stock_move1, stock_move2 = sorted(purchase.moves,
... key=lambda m: m.quantity)
>>> assertEqual(invoice_line1.stock_moves, [stock_move1])
>>> assertEqual(stock_move1.invoice_lines, [invoice_line1])
>>> assertEqual(invoice_line2.stock_moves, [stock_move2])
>>> assertEqual(stock_move2.invoice_lines, [invoice_line2])
Check actual quantity::
>>> for line in purchase.lines:
... assertEqual(line.quantity, line.actual_quantity)
Post invoice and check no new invoices::
>>> invoice.invoice_date = today
>>> invoice.click('post')
>>> purchase.reload()
>>> purchase.shipment_state
'waiting'
>>> purchase.invoice_state
'awaiting payment'
>>> len(purchase.moves), len(purchase.shipment_returns), len(purchase.invoices)
(2, 0, 1)
Purchase 5 products with an invoice method 'on shipment'::
>>> purchase = Purchase()
>>> purchase.party = supplier
>>> purchase.payment_term = payment_term
>>> purchase.invoice_method = 'shipment'
>>> purchase_line = PurchaseLine()
>>> purchase.lines.append(purchase_line)
>>> purchase_line.product = product
>>> purchase_line.quantity = 2.0
>>> purchase_line = PurchaseLine()
>>> purchase.lines.append(purchase_line)
>>> purchase_line.type = 'comment'
>>> purchase_line.description = 'Comment'
>>> purchase_line = PurchaseLine()
>>> purchase.lines.append(purchase_line)
>>> purchase_line.product = product
>>> purchase_line.quantity = 3.0
>>> purchase.click('quote')
>>> purchase.click('confirm')
>>> purchase.state
'processing'
>>> purchase.shipment_state
'waiting'
>>> purchase.invoice_state
'none'
>>> len(purchase.moves), len(purchase.shipment_returns), len(purchase.invoices)
(2, 0, 0)
Not yet linked to invoice lines::
>>> stock_move1, stock_move2 = sorted(purchase.moves,
... key=lambda m: m.quantity)
>>> len(stock_move1.invoice_lines)
0
>>> len(stock_move2.invoice_lines)
0
Validate Shipments::
>>> Move = Model.get('stock.move')
>>> ShipmentIn = Model.get('stock.shipment.in')
>>> shipment = ShipmentIn()
>>> shipment.supplier = supplier
>>> for move in purchase.moves:
... incoming_move = Move(id=move.id)
... shipment.incoming_moves.append(incoming_move)
>>> shipment.save()
>>> assertEqual(shipment.origins, purchase.rec_name)
>>> shipment.click('receive')
>>> shipment.click('do')
>>> purchase.reload()
>>> purchase.shipment_state
'received'
>>> len(purchase.shipments), len(purchase.shipment_returns)
(1, 0)
Open supplier invoice::
>>> purchase.invoice_state
'pending'
>>> invoice, = purchase.invoices
>>> invoice.type
'in'
>>> invoice_line1, invoice_line2 = sorted(invoice.lines,
... key=lambda l: l.quantity or 0)
>>> for line in invoice.lines:
... line.quantity = 1
... line.save()
>>> invoice.invoice_date = today
>>> invoice.click('post')
Invoice lines must be linked to each stock moves::
>>> assertEqual(invoice_line1.stock_moves, [stock_move1])
>>> assertEqual(invoice_line2.stock_moves, [stock_move2])
Check second invoices::
>>> purchase.reload()
>>> len(purchase.invoices)
2
>>> sum(l.quantity for i in purchase.invoices for l in i.lines)
5.0
Create the report::
>>> purchase_report = Report('purchase.purchase')
>>> ext, _, _, name = purchase_report.execute([purchase], {})
>>> ext
'odt'
>>> name
'Purchase-2'
Create a Return::
>>> return_ = Purchase()
>>> return_.party = supplier
>>> return_.payment_term = payment_term
>>> return_.invoice_method = 'shipment'
>>> return_line = PurchaseLine()
>>> return_.lines.append(return_line)
>>> return_line.product = product
>>> return_line.quantity = -4.
>>> return_line = PurchaseLine()
>>> return_.lines.append(return_line)
>>> return_line.type = 'comment'
>>> return_line.description = 'Comment'
>>> return_.click('quote')
>>> return_.click('confirm')
>>> return_.state
'processing'
>>> return_.shipment_state
'waiting'
>>> return_.invoice_state
'none'
>>> (len(return_.shipments), len(return_.shipment_returns),
... len(return_.invoices))
(0, 1, 0)
Check Return Shipments::
>>> ShipmentReturn = Model.get('stock.shipment.in.return')
>>> ship_return, = return_.shipment_returns
>>> ship_return.state
'waiting'
>>> move_return, = ship_return.moves
>>> move_return.product.rec_name
'product'
>>> move_return.quantity
4.0
>>> ship_return.click('assign_try')
>>> ship_return.click('do')
>>> ship_return.state
'done'
>>> return_.reload()
>>> return_.state
'processing'
>>> return_.shipment_state
'received'
>>> return_.invoice_state
'pending'
Open supplier credit note::
>>> credit_note, = return_.invoices
>>> credit_note.type
'in'
>>> len(credit_note.lines)
1
>>> sum(l.quantity for l in credit_note.lines)
-4.0
>>> credit_note.invoice_date = today
>>> credit_note.click('post')
Mixing return and purchase::
>>> mix = Purchase()
>>> mix.party = supplier
>>> mix.payment_term = payment_term
>>> mix.invoice_method = 'order'
>>> mixline = PurchaseLine()
>>> mix.lines.append(mixline)
>>> mixline.product = product
>>> mixline.quantity = 7.
>>> mixline_comment = PurchaseLine()
>>> mix.lines.append(mixline_comment)
>>> mixline_comment.type = 'comment'
>>> mixline_comment.description = 'Comment'
>>> mixline2 = PurchaseLine()
>>> mix.lines.append(mixline2)
>>> mixline2.product = product
>>> mixline2.quantity = -2.
>>> mix.click('quote')
>>> mix.click('confirm')
>>> mix.state
'processing'
>>> mix.shipment_state
'waiting'
>>> mix.invoice_state
'pending'
>>> len(mix.moves), len(mix.shipment_returns), len(mix.invoices)
(2, 1, 1)
Checking Shipments::
>>> mix_return, = mix.shipment_returns
>>> mix_shipment = ShipmentIn()
>>> mix_shipment.supplier = supplier
>>> for move in mix.moves:
... if move.id in [m.id for m in mix_return.moves]:
... continue
... incoming_move = Move(id=move.id)
... mix_shipment.incoming_moves.append(incoming_move)
>>> mix_shipment.click('receive')
>>> mix_shipment.click('do')
>>> mix.reload()
>>> len(mix.shipments)
1
>>> mix_return.click('wait')
>>> mix_return.click('assign_try')
>>> mix_return.click('do')
>>> move_return, = mix_return.moves
>>> move_return.product.rec_name
'product'
>>> move_return.quantity
2.0
Checking the invoice::
>>> mix.reload()
>>> mix_invoice, = mix.invoices
>>> mix_invoice.type
'in'
>>> len(mix_invoice.lines)
2
>>> sorted(l.quantity for l in mix_invoice.lines)
[-2.0, 7.0]
>>> mix_invoice.invoice_date = today
>>> mix_invoice.click('post')
Mixing stuff with an invoice method 'on shipment'::
>>> mix = Purchase()
>>> mix.party = supplier
>>> mix.payment_term = payment_term
>>> mix.invoice_method = 'shipment'
>>> mixline = PurchaseLine()
>>> mix.lines.append(mixline)
>>> mixline.product = product
>>> mixline.quantity = 6.
>>> mixline_comment = PurchaseLine()
>>> mix.lines.append(mixline_comment)
>>> mixline_comment.type = 'comment'
>>> mixline_comment.description = 'Comment'
>>> mixline2 = PurchaseLine()
>>> mix.lines.append(mixline2)
>>> mixline2.product = product
>>> mixline2.quantity = -3.
>>> mix.click('quote')
>>> mix.click('confirm')
>>> mix.state
'processing'
>>> mix.shipment_state
'waiting'
>>> mix.invoice_state
'none'
>>> len(mix.moves), len(mix.shipment_returns), len(mix.invoices)
(2, 1, 0)
Checking Shipments::
>>> mix_return, = mix.shipment_returns
>>> mix_shipment = ShipmentIn()
>>> mix_shipment.supplier = supplier
>>> for move in mix.moves:
... if move.id in [m.id for m in mix_return.moves]:
... continue
... incoming_move = Move(id=move.id)
... mix_shipment.incoming_moves.append(incoming_move)
>>> mix_shipment.click('receive')
>>> mix_shipment.click('do')
>>> mix.reload()
>>> len(mix.shipments)
1
>>> mix_return.click('wait')
>>> mix_return.click('assign_try')
>>> mix_return.click('do')
>>> move_return, = mix_return.moves
>>> move_return.product.rec_name
'product'
>>> move_return.quantity
3.0
Purchase services::
>>> service_purchase = Purchase()
>>> service_purchase.party = supplier
>>> service_purchase.payment_term = payment_term
>>> purchase_line = service_purchase.lines.new()
>>> purchase_line.product = service
>>> purchase_line.quantity = 1
>>> purchase_line.unit_price = Decimal('10.0000')
>>> service_purchase.save()
>>> service_purchase.click('quote')
>>> service_purchase.click('confirm')
>>> service_purchase.state
'processing'
>>> service_purchase.shipment_state
'none'
>>> service_purchase.invoice_state
'pending'
>>> service_invoice, = service_purchase.invoices
Pay the service invoice::
>>> service_invoice.invoice_date = today
>>> service_invoice.click('post')
>>> pay = service_invoice.click('pay')
>>> pay.form.payment_method = payment_method
>>> pay.form.amount = service_invoice.total_amount
>>> pay.execute('choice')
>>> service_invoice.reload()
>>> service_invoice.state
'paid'
Check service purchase states::
>>> service_purchase.reload()
>>> service_purchase.invoice_state
'paid'
>>> service_purchase.shipment_state
'none'
>>> service_purchase.state
'done'
Create a purchase to be invoiced on shipment partially and check correctly
linked to invoices::
>>> purchase = Purchase()
>>> purchase.party = supplier
>>> purchase.payment_term = payment_term
>>> purchase.invoice_method = 'shipment'
>>> line = purchase.lines.new()
>>> line.product = product
>>> line.quantity = 10.0
>>> purchase.click('quote')
>>> purchase.click('confirm')
>>> shipment = ShipmentIn()
>>> shipment.supplier = supplier
>>> for move in purchase.moves:
... incoming_move = Move(id=move.id)
... incoming_move.quantity = 5.0
... shipment.incoming_moves.append(incoming_move)
>>> shipment.save()
>>> for move in shipment.inventory_moves:
... move.quantity = 5.0
>>> shipment.click('receive')
>>> shipment.click('do')
>>> purchase.reload()
>>> invoice, = purchase.invoices
>>> invoice_line, = invoice.lines
>>> invoice_line.quantity
5.0
>>> stock_move, = invoice_line.stock_moves
>>> stock_move.quantity
5.0
>>> stock_move.state
'done'
Create a purchase to be invoiced on order, partially send it and check
correctly linked to invoices::
>>> purchase = Purchase()
>>> purchase.party = supplier
>>> purchase.payment_term = payment_term
>>> purchase.invoice_method = 'order'
>>> line = purchase.lines.new()
>>> line.product = product
>>> line.quantity = 10.0
>>> purchase.click('quote')
>>> purchase.click('confirm')
>>> shipment = ShipmentIn()
>>> shipment.supplier = supplier
>>> for move in purchase.moves:
... incoming_move = Move(id=move.id)
... incoming_move.quantity = 8.0
... shipment.incoming_moves.append(incoming_move)
>>> shipment.save()
>>> for move in shipment.inventory_moves:
... move.quantity = 8.0
>>> shipment.click('receive')
>>> shipment.click('do')
>>> purchase.reload()
>>> invoice, = purchase.invoices
>>> invoice_line, = invoice.lines
>>> invoice_line.quantity
10.0
>>> draft_stock_move, stock_move = sorted(
... invoice_line.stock_moves, key=lambda m: m.quantity)
>>> draft_stock_move.quantity
2.0
>>> draft_stock_move.state
'draft'
>>> stock_move.quantity
8.0
>>> stock_move.state
'done'
Deleting a line from a invoice should recreate it::
>>> purchase = Purchase()
>>> purchase.party = customer
>>> line = purchase.lines.new()
>>> line.product = product
>>> line.quantity = 10.0
>>> line.unit_price = Decimal('5.0000')
>>> purchase.click('quote')
>>> purchase.click('confirm')
>>> invoice, = purchase.invoices
>>> invoice_line, = invoice.lines
>>> invoice.lines.remove(invoice_line)
>>> invoice.invoice_date = today
>>> invoice.click('post')
>>> purchase.reload()
>>> new_invoice, = purchase.invoices
>>> new_invoice.number
>>> len(new_invoice.lines)
1

View File

@@ -0,0 +1,52 @@
========================================
Purchase Copy Product Suppliers Scenario
========================================
Imports::
>>> from decimal import Decimal
>>> from proteus import Model
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules, assertEqual
Activate modules::
>>> config = activate_modules('purchase', create_company)
Create party::
>>> Party = Model.get('party.party')
>>> supplier = Party(name='Supplier')
>>> supplier.save()
Create a product with suppliers::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> ProductTemplate = Model.get('product.template')
>>> template = ProductTemplate()
>>> template.name = 'product'
>>> template.default_uom = unit
>>> template.type = 'goods'
>>> template.purchasable = True
>>> template.list_price = Decimal('10')
>>> template.cost_price_method = 'fixed'
>>> product_supplier = template.product_suppliers.new()
>>> product_supplier.party = supplier
>>> template.save()
>>> product, = template.products
>>> product_supplier = product.product_suppliers.new()
>>> product_supplier.party = supplier
>>> assertEqual(product_supplier.template, template)
>>> product.save()
Supplier is copied when copying the template::
>>> template_copy, = template.duplicate()
>>> product_copy, = template_copy.products
>>> len(template_copy.product_suppliers)
2
>>> len(product_copy.product_suppliers)
1

View File

@@ -0,0 +1,59 @@
===========================
Purchase with Default Taxes
===========================
Imports::
>>> from decimal import Decimal
>>> from proteus import Model
>>> from trytond.modules.account.tests.tools import (
... create_chart, create_tax, get_accounts)
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules, assertEqual
Activate modules::
>>> config = activate_modules('purchase', create_company, create_chart)
>>> Party = Model.get('party.party')
>>> Purchase = Model.get('purchase.purchase')
>>> Tax = Model.get('account.tax')
Get accounts::
>>> accounts = get_accounts()
Create tax::
>>> tax = create_tax(Decimal('.10'))
>>> tax.save()
>>> accounts['expense'].taxes.append(Tax(tax.id))
>>> accounts['expense'].save()
Create parties::
>>> supplier = Party(name="Supplier")
>>> supplier.save()
Create a purchase without product::
>>> purchase = Purchase(party=supplier)
>>> line = purchase.lines.new()
>>> assertEqual(line.taxes, [tax])
>>> line.quantity = 1
>>> line.unit_price = Decimal('100.0000')
>>> purchase.click('quote')
>>> purchase.total_amount
Decimal('110.00')
>>> purchase.click('confirm')
>>> purchase.state
'processing'
Check invoice::
>>> invoice, = purchase.invoices
>>> invoice.total_amount
Decimal('110.00')
>>> line, = invoice.lines
>>> assertEqual(line.account, accounts['expense'])

View File

@@ -0,0 +1,48 @@
=======================
Purchase Empty Scenario
=======================
Imports::
>>> from proteus import Model
>>> from trytond.modules.account.tests.tools import create_chart
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules
Activate modules::
>>> config = activate_modules('purchase', create_company, create_chart)
Create parties::
>>> Party = Model.get('party.party')
>>> supplier = Party(name='Supplier')
>>> supplier.save()
Create empty purchase::
>>> Purchase = Model.get('purchase.purchase')
>>> purchase = Purchase()
>>> purchase.party = supplier
>>> purchase.click('quote')
>>> purchase.state
'quotation'
>>> purchase.untaxed_amount
Decimal('0')
>>> purchase.tax_amount
Decimal('0')
>>> purchase.total_amount
Decimal('0')
>>> purchase.click('confirm')
>>> purchase.state
'done'
>>> purchase.shipment_state
'none'
>>> len(purchase.moves)
0
>>> len(purchase.shipment_returns)
0
>>> purchase.invoice_state
'none'
>>> len(purchase.invoices)
0

View File

@@ -0,0 +1,103 @@
================================
Purchase Line Cancelled Scenario
================================
Imports::
>>> from decimal import Decimal
>>> from proteus import Model
>>> from trytond.modules.account.tests.tools import create_chart, get_accounts
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules
Activate modules::
>>> config = activate_modules('purchase', create_company, create_chart)
>>> Party = Model.get('party.party')
>>> ProductCategory = Model.get('product.category')
>>> ProductTemplate = Model.get('product.template')
>>> ProductUom = Model.get('product.uom')
>>> Purchase = Model.get('purchase.purchase')
Get accounts::
>>> accounts = get_accounts()
Create party::
>>> supplier = Party(name="Supplier")
>>> supplier.save()
Create product::
>>> account_category = ProductCategory(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_expense = accounts['expense']
>>> account_category.save()
>>> template = ProductTemplate(name="Product")
>>> template.default_uom, = ProductUom.find([('name', '=', "Unit")])
>>> template.type = 'goods'
>>> template.purchasable = True
>>> template.account_category = account_category
>>> template.save()
>>> product, = template.products
Purchase product::
>>> purchase = Purchase(party=supplier)
>>> line = purchase.lines.new()
>>> line.product = product
>>> line.quantity = 1
>>> line.unit_price = Decimal('10.0000')
>>> purchase.click('quote')
>>> purchase.click('confirm')
>>> purchase.state
'processing'
>>> purchase.shipment_state
'waiting'
>>> purchase.invoice_state
'pending'
Cancel stock move and invoice::
>>> move, = purchase.moves
>>> move.click('cancel')
>>> move.state
'cancelled'
>>> invoice, = purchase.invoices
>>> invoice.click('cancel')
>>> invoice.state
'cancelled'
>>> purchase.reload()
>>> purchase.state
'processing'
>>> purchase.shipment_state
'exception'
>>> purchase.invoice_state
'exception'
Ignore exceptions::
>>> invoice_handle_exception = purchase.click('handle_invoice_exception')
>>> invoice_handle_exception.form.ignore_invoices.extend(
... invoice_handle_exception.form.ignore_invoices.find())
>>> invoice_handle_exception.execute('handle')
>>> purchase.invoice_state
'none'
>>> shipment_handle_exception = purchase.click('handle_shipment_exception')
>>> shipment_handle_exception.form.ignore_moves.extend(
... shipment_handle_exception.form.ignore_moves.find())
>>> shipment_handle_exception.execute('handle')
>>> purchase.shipment_state
'none'
>>> purchase.state
'done'

View File

@@ -0,0 +1,91 @@
============================================
Purchase Line Cancelled On Shipment Scenario
============================================
Imports::
>>> from decimal import Decimal
>>> from proteus import Model
>>> from trytond.modules.account.tests.tools import create_chart, get_accounts
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules
Activate modules::
>>> config = activate_modules('purchase', create_company, create_chart)
>>> Party = Model.get('party.party')
>>> ProductCategory = Model.get('product.category')
>>> ProductTemplate = Model.get('product.template')
>>> ProductUom = Model.get('product.uom')
>>> Purchase = Model.get('purchase.purchase')
Get accounts::
>>> accounts = get_accounts()
Create party::
>>> supplier = Party(name="Supplier")
>>> supplier.save()
Create product::
>>> account_category = ProductCategory(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_expense = accounts['expense']
>>> account_category.save()
>>> template = ProductTemplate(name="Product")
>>> template.default_uom, = ProductUom.find([('name', '=', "Unit")])
>>> template.type = 'goods'
>>> template.purchasable = True
>>> template.account_category = account_category
>>> template.save()
>>> product, = template.products
Purchase product::
>>> purchase = Purchase(party=supplier)
>>> purchase.invoice_method = 'shipment'
>>> line = purchase.lines.new()
>>> line.product = product
>>> line.quantity = 1
>>> line.unit_price = Decimal('10.0000')
>>> purchase.click('quote')
>>> purchase.click('confirm')
>>> purchase.state
'processing'
>>> purchase.shipment_state
'waiting'
>>> purchase.invoice_state
'none'
Cancel stock move::
>>> move, = purchase.moves
>>> move.click('cancel')
>>> move.state
'cancelled'
>>> purchase.reload()
>>> purchase.state
'processing'
>>> purchase.shipment_state
'exception'
>>> purchase.invoice_state
'none'
Ignore exception::
>>> shipment_handle_exception = purchase.click('handle_shipment_exception')
>>> shipment_handle_exception.form.ignore_moves.extend(
... shipment_handle_exception.form.ignore_moves.find())
>>> shipment_handle_exception.execute('handle')
>>> purchase.shipment_state
'none'
>>> purchase.state
'done'

View File

@@ -0,0 +1,90 @@
================================
Purchase Manual Invoice Scenario
================================
Imports::
>>> from decimal import Decimal
>>> from proteus import Model
>>> from trytond.modules.account.tests.tools import create_chart, get_accounts
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules
Activate modules::
>>> config = activate_modules('purchase', create_company, create_chart)
>>> Party = Model.get('party.party')
>>> ProductCategory = Model.get('product.category')
>>> ProductTemplate = Model.get('product.template')
>>> ProductUom = Model.get('product.uom')
>>> Purchase = Model.get('purchase.purchase')
Get accounts::
>>> accounts = get_accounts()
Create party::
>>> supplier = Party(name='Supplier')
>>> supplier.save()
Create account category::
>>> account_category = ProductCategory(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_expense = accounts['expense']
>>> account_category.save()
Create product::
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> template = ProductTemplate()
>>> template.name = 'product'
>>> template.default_uom = unit
>>> template.type = 'service'
>>> template.purchasable = True
>>> template.account_category = account_category
>>> template.save()
>>> product, = template.products
Purchase with manual invoice method::
>>> purchase = Purchase()
>>> purchase.party = supplier
>>> purchase.invoice_method = 'manual'
>>> line = purchase.lines.new()
>>> line.product = product
>>> line.quantity = 10
>>> line.unit_price = Decimal('5.0000')
>>> purchase.click('quote')
>>> purchase.click('confirm')
>>> purchase.state
'processing'
>>> purchase.invoice_state
'none'
>>> len(purchase.invoices)
0
Manually create an invoice::
>>> purchase.click('manual_invoice')
>>> purchase.state
'processing'
>>> purchase.invoice_state
'pending'
Change quantity on invoice and create a new invoice::
>>> invoice, = purchase.invoices
>>> line, = invoice.lines
>>> line.quantity = 5
>>> invoice.save()
>>> len(purchase.invoices)
1
>>> purchase.click('manual_invoice')
>>> len(purchase.invoices)
2

View File

@@ -0,0 +1,92 @@
===============================
Purchase Modify Header Scenario
===============================
Imports::
>>> from decimal import Decimal
>>> from proteus import Model
>>> from trytond.modules.account.tests.tools import (
... create_chart, create_tax, get_accounts)
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules, assertEqual
Activate modules::
>>> config = activate_modules('purchase', create_company, create_chart)
Get accounts::
>>> accounts = get_accounts()
>>> expense = accounts['expense']
Create tax and tax rule::
>>> tax = create_tax(Decimal('.10'))
>>> tax.save()
>>> TaxRule = Model.get('account.tax.rule')
>>> foreign = TaxRule(name='Foreign Suppliers')
>>> no_tax = foreign.lines.new()
>>> no_tax.origin_tax = tax
>>> foreign.save()
Create account categories::
>>> ProductCategory = Model.get('product.category')
>>> account_category = ProductCategory(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_expense = expense
>>> account_category.supplier_taxes.append(tax)
>>> account_category.save()
Create product::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> ProductTemplate = Model.get('product.template')
>>> template = ProductTemplate()
>>> template.name = 'product'
>>> template.default_uom = unit
>>> template.type = 'goods'
>>> template.purchasable = True
>>> template.list_price = Decimal('10')
>>> template.account_category = account_category
>>> template.save()
>>> product, = template.products
Create parties::
>>> Party = Model.get('party.party')
>>> supplier = Party(name='Supplier')
>>> supplier.save()
>>> another = Party(name='Another Supplier', supplier_tax_rule=foreign)
>>> another.save()
Create a sale with a line::
>>> Purchase = Model.get('purchase.purchase')
>>> purchase = Purchase()
>>> purchase.party = supplier
>>> purchase_line = purchase.lines.new()
>>> purchase_line.product = product
>>> purchase_line.quantity = 3
>>> purchase_line.unit_price = Decimal('5.0000')
>>> purchase_line_comment = purchase.lines.new(type='comment')
>>> purchase.save()
>>> purchase.untaxed_amount, purchase.tax_amount, purchase.total_amount
(Decimal('15.00'), Decimal('1.50'), Decimal('16.50'))
Change the party::
>>> modify_header = purchase.click('modify_header')
>>> assertEqual(modify_header.form.party, supplier)
>>> modify_header.form.party = another
>>> modify_header.execute('modify')
>>> purchase.party.name
'Another Supplier'
>>> purchase.untaxed_amount, purchase.tax_amount, purchase.total_amount
(Decimal('15.00'), Decimal('0'), Decimal('15.00'))

View File

@@ -0,0 +1,145 @@
===========================
Purchase Reporting Scenario
===========================
Imports::
>>> from decimal import Decimal
>>> from dateutil.relativedelta import relativedelta
>>> from proteus import Model
>>> from trytond.modules.account.tests.tools import (
... create_chart, create_fiscalyear, get_accounts)
>>> from trytond.modules.account_invoice.tests.tools import (
... set_fiscalyear_invoice_sequences)
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules, assertEqual
Activate modules::
>>> config = activate_modules('purchase', create_company, create_chart)
>>> Party = Model.get('party.party')
>>> ProductUom = Model.get('product.uom')
>>> ProductTemplate = Model.get('product.template')
>>> Purchase = Model.get('purchase.purchase')
>>> Supplier = Model.get('purchase.reporting.supplier')
>>> SupplierTimeseries = Model.get(
... 'purchase.reporting.supplier.time_series')
>>> Product = Model.get('purchase.reporting.product')
>>> ProductTimeseries = Model.get('purchase.reporting.product.time_series')
Create fiscal year::
>>> fiscalyear = set_fiscalyear_invoice_sequences(create_fiscalyear())
>>> fiscalyear.click('create_period')
Get accounts::
>>> accounts = get_accounts()
>>> expense = accounts['expense']
Create parties::
>>> supplier1 = Party(name='Supplier1')
>>> supplier1.save()
>>> supplier2 = Party(name='Supplier2')
>>> supplier2.save()
Create products::
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> template1 = ProductTemplate()
>>> template1.name = "Product1"
>>> template1.default_uom = unit
>>> template1.type = 'service'
>>> template1.purchasable = True
>>> template1.list_price = Decimal('20')
>>> template1.save()
>>> product1, = template1.products
>>> template2, = template1.duplicate(default={'name': "Product2"})
>>> product2, = template2.products
Create purchases::
>>> purchase1 = Purchase()
>>> purchase1.party = supplier1
>>> purchase1.purchase_date = fiscalyear.start_date
>>> line = purchase1.lines.new()
>>> line.product = product1
>>> line.quantity = 2
>>> line.unit_price = Decimal('10.0000')
>>> line = purchase1.lines.new()
>>> line.product = product2
>>> line.quantity = 1
>>> line.unit_price = Decimal('10.0000')
>>> purchase1.click('quote')
>>> purchase1.click('confirm')
>>> purchase2 = Purchase()
>>> purchase2.party = supplier2
>>> purchase2.purchase_date = (
... fiscalyear.start_date + relativedelta(months=1))
>>> line = purchase2.lines.new()
>>> line.product = product1
>>> line.quantity = 1
>>> line.unit_price = Decimal('10.0000')
>>> purchase2.click('quote')
>>> purchase2.click('confirm')
Check purchase reporting per supplier::
>>> context = dict(
... from_date=fiscalyear.start_date,
... to_date=fiscalyear.end_date,
... period='month')
>>> with config.set_context(context=context):
... reports = Supplier.find([])
... time_series = SupplierTimeseries.find([])
>>> len(reports)
2
>>> with config.set_context(context=context):
... assertEqual({(r.supplier.id, r.number, r.expense) for r in reports},
... {(supplier1.id, 1, Decimal('30')),
... (supplier2.id, 1, Decimal('10'))})
>>> len(time_series)
2
>>> purchase1_ts_date = purchase1.purchase_date.replace(day=1)
>>> purchase2_ts_date = purchase2.purchase_date.replace(day=1)
>>> with config.set_context(context=context):
... assertEqual({(r.supplier.id, r.date, r.number, r.expense)
... for r in time_series},
... {(supplier1.id, purchase1_ts_date, 1, Decimal('30')),
... (supplier2.id, purchase2_ts_date, 1, Decimal('10'))})
Check purchase reporting per product without supplier::
>>> with config.set_context(context=context):
... reports = Product.find([])
... time_series = ProductTimeseries.find([])
>>> len(reports)
0
Check purchase reporting per product with supplier::
>>> context['supplier'] = supplier1.id
>>> context['currency'] = purchase1.currency.id
>>> with config.set_context(context=context):
... reports = Product.find([])
... time_series = ProductTimeseries.find([])
>>> len(reports)
2
>>> with config.set_context(context=context):
... assertEqual({(r.product.id, r.number, r.expense) for r in reports},
... {(product1.id, 1, Decimal('20')),
... (product2.id, 1, Decimal('10'))})
>>> len(time_series)
2
>>> with config.set_context(context=context):
... assertEqual({(r.product.id, r.date, r.number, r.expense)
... for r in time_series},
... {(product1.id, purchase1_ts_date, 1, Decimal('20')),
... (product2.id, purchase1_ts_date, 1, Decimal('10'))})

View File

@@ -0,0 +1,86 @@
===============================
Purchase Return Wizard Scenario
===============================
Imports::
>>> from decimal import Decimal
>>> from proteus import Model, Wizard
>>> from trytond.modules.account.tests.tools import (
... create_chart, create_fiscalyear, get_accounts)
>>> from trytond.modules.account_invoice.tests.tools import (
... set_fiscalyear_invoice_sequences)
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules, assertEqual
Activate modules::
>>> config = activate_modules('purchase', create_company, create_chart)
Create fiscal year::
>>> fiscalyear = set_fiscalyear_invoice_sequences(create_fiscalyear())
>>> fiscalyear.click('create_period')
Get accounts::
>>> accounts = get_accounts()
>>> expense = accounts['expense']
Create parties::
>>> Party = Model.get('party.party')
>>> supplier = Party(name='Supplier')
>>> supplier.save()
Create account categories::
>>> ProductCategory = Model.get('product.category')
>>> account_category = ProductCategory(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_expense = expense
>>> account_category.save()
Create product::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> ProductTemplate = Model.get('product.template')
>>> template = ProductTemplate()
>>> template.name = 'service'
>>> template.default_uom = unit
>>> template.type = 'service'
>>> template.purchasable = True
>>> template.list_price = Decimal('10')
>>> template.cost_price_method = 'fixed'
>>> template.account_category = account_category
>>> template.save()
>>> service, = template.products
Return purchase using the wizard::
>>> Purchase = Model.get('purchase.purchase')
>>> purchase_to_return = Purchase()
>>> purchase_to_return.party = supplier
>>> purchase_line = purchase_to_return.lines.new()
>>> purchase_line.product = service
>>> purchase_line.quantity = 1
>>> purchase_line.unit_price = Decimal('10.0000')
>>> purchase_line = purchase_to_return.lines.new()
>>> purchase_line.type = 'comment'
>>> purchase_line.description = 'Test comment'
>>> purchase_to_return.click('quote')
>>> purchase_to_return.click('confirm')
>>> purchase_to_return.state
'processing'
>>> return_purchase = Wizard('purchase.return_purchase', [
... purchase_to_return])
>>> return_purchase.execute('return_')
>>> returned_purchase, = Purchase.find([
... ('state', '=', 'draft'),
... ])
>>> assertEqual(returned_purchase.origin, purchase_to_return)
>>> sorted([x.quantity or 0 for x in returned_purchase.lines])
[-1.0, 0]

View File

@@ -0,0 +1,130 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime as dt
from decimal import Decimal
from trytond.modules.account.tests import create_chart
from trytond.modules.company.tests import (
CompanyTestMixin, PartyCompanyCheckEraseMixin, create_company, set_company)
from trytond.modules.party.tests import PartyCheckReplaceMixin
from trytond.pool import Pool
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
from trytond.transaction import Transaction
class PurchaseTestCase(
PartyCompanyCheckEraseMixin, PartyCheckReplaceMixin, CompanyTestMixin,
ModuleTestCase):
'Test Purchase module'
module = 'purchase'
@with_transaction()
def test_purchase_price(self):
'Test purchase price'
pool = Pool()
Account = pool.get('account.account')
Template = pool.get('product.template')
Product = pool.get('product.product')
Uom = pool.get('product.uom')
ProductSupplier = pool.get('purchase.product_supplier')
Party = pool.get('party.party')
Purchase = pool.get('purchase.purchase')
company = create_company()
with set_company(company):
create_chart(company)
receivable, = Account.search([
('closed', '!=', True),
('type.receivable', '=', True),
('party_required', '=', True),
('company', '=', company.id),
], limit=1)
payable, = Account.search([
('closed', '!=', True),
('type.payable', '=', True),
('party_required', '=', True),
('company', '=', company.id),
], limit=1)
kg, = Uom.search([('name', '=', 'Kilogram')])
g, = Uom.search([('name', '=', 'Gram')])
template, = Template.create([{
'name': 'Product',
'default_uom': g.id,
'purchase_uom': kg.id,
'list_price': Decimal(5),
'purchasable': True,
'products': [('create', [{
'cost_price': Decimal(3),
}])],
}])
product, = template.products
supplier, = Party.create([{
'name': 'Supplier',
'account_receivable': receivable.id,
'account_payable': payable.id,
'addresses': [('create', [{}])],
}])
product_supplier, = ProductSupplier.create([{
'template': template.id,
'party': supplier.id,
'prices': [('create', [{
'sequence': 1,
'quantity': 1,
'unit_price': Decimal(3000),
}, {
'sequence': 2,
'quantity': 2,
'unit_price': Decimal(2500),
}])],
}])
purchase, = Purchase.create([{
'party': supplier.id,
'invoice_address': supplier.addresses[0].id,
'purchase_date': dt.date.today(),
'lines': [('create', [{
'product': product.id,
'quantity': 10,
'unit': kg.id,
'unit_price': Decimal(2000),
}])],
}])
purchase.state = 'confirmed'
purchase.save()
prices = Product.get_purchase_price([product], quantity=100)
self.assertEqual(prices, {product.id: Decimal(2)})
prices = Product.get_purchase_price([product], quantity=1500)
self.assertEqual(prices, {product.id: Decimal(2)})
with Transaction().set_context(uom=kg.id):
prices = Product.get_purchase_price([product], quantity=0.5)
self.assertEqual(prices, {product.id: Decimal(2000)})
prices = Product.get_purchase_price([product], quantity=1.5)
self.assertEqual(prices, {product.id: Decimal(2000)})
with Transaction().set_context(supplier=supplier.id):
prices = Product.get_purchase_price([product], quantity=100)
self.assertEqual(prices, {product.id: Decimal(2)})
prices = Product.get_purchase_price([product], quantity=1500)
self.assertEqual(prices, {product.id: Decimal(3)})
prices = Product.get_purchase_price([product], quantity=3000)
self.assertEqual(prices, {product.id: Decimal('2.5')})
with Transaction().set_context(uom=kg.id, supplier=supplier.id):
prices = Product.get_purchase_price([product], quantity=0.5)
self.assertEqual(prices, {product.id: Decimal(2000)})
prices = Product.get_purchase_price([product], quantity=1.5)
self.assertEqual(prices, {product.id: Decimal(3000)})
prices = Product.get_purchase_price([product], quantity=3)
self.assertEqual(prices, {product.id: Decimal(2500)})
prices = Product.get_purchase_price([product], quantity=-4)
self.assertEqual(prices, {product.id: Decimal(2500)})
del ModuleTestCase

View File

@@ -0,0 +1,8 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.tests.test_tryton import load_doc_tests
def load_tests(*args, **kwargs):
return load_doc_tests(__name__, __file__, *args, **kwargs)

View File

@@ -0,0 +1,69 @@
[tryton]
version=7.8.1
depends:
account
account_invoice
account_invoice_stock
account_product
company
currency
ir
party
product
res
stock
xml:
purchase.xml
purchase_reporting.xml
configuration.xml
party.xml
stock.xml
product.xml
invoice.xml
message.xml
[register]
model:
party.Party
party.CustomerCode
party.SupplierLeadTime
party.PartySupplierCurrency
product.Template
product.Product
product.ProductSupplier
product.ProductSupplierPrice
stock.Location
stock.Move
stock.ShipmentIn
stock.ShipmentInReturn
invoice.Invoice
invoice.InvoiceLine
configuration.Configuration
configuration.ConfigurationSequence
configuration.ConfigurationPurchaseMethod
purchase.Purchase
purchase.PurchaseIgnoredInvoice
purchase.PurchaseRecreatedInvoice
purchase.Line
purchase.LineTax
purchase.LineIgnoredMove
purchase.LineRecreatedMove
purchase.HandleShipmentExceptionAsk
purchase.HandleInvoiceExceptionAsk
purchase.ReturnPurchaseStart
purchase_reporting.Context
purchase_reporting.Main
purchase_reporting.MainTimeseries
purchase_reporting.Supplier
purchase_reporting.SupplierTimeseries
purchase_reporting.Product
purchase_reporting.ProductTimeseries
wizard:
purchase.HandleShipmentException
purchase.HandleInvoiceException
party.PartyReplace
party.PartyErase
purchase.ModifyHeader
purchase.ReturnPurchase
report:
purchase.PurchaseReport

View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<label name="purchase_sequence"/>
<field name="purchase_sequence"/>
<label name="purchase_invoice_method" />
<field name="purchase_invoice_method" />
<newline/>
<label name="purchase_process_after"/>
<field name="purchase_process_after"/>
</form>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<image name="tryton-question" xexpand="0" xfill="0"/>
<label string="Choose cancelled invoices to ignore or recreate:" id="choose" yalign="0.5" xalign="0.0" xexpand="1" colspan="3"/>
<field name="ignore_invoices" colspan="2"/>
<field name="recreate_invoices" colspan="2"/>
</form>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<image name="tryton-question" xexpand="0" xfill="0"/>
<label string="Choose cancelled stock moves to ignore or recreate:" id="choose" yalign="0.5" xalign="0.0" xexpand="1" colspan="3"/>
<field name="ignore_moves" colspan="2" view_ids="stock.move_view_tree_simple"/>
<field name="recreate_moves" colspan="2" view_ids="stock.move_view_tree_simple"/>
</form>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="/form/field[@name='input_location']" position="after">
<label name="supplier_return_location"/>
<field name="supplier_return_location"/>
</xpath>
</data>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='lines']" position="replace_attributes">
<field name="lines" invisible="1"/>
</xpath>
</data>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="product" expand="1"/>
<field name="from_location" expand="1"/>
<field name="to_location" expand="1"/>
<field name="quantity" symbol="unit"/>
<field name="state"/>
<field name="purchase_exception_state"/>
<button name="cancel" multiple="1"/>
<button name="draft" multiple="1"/>
</tree>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='shipment']" position="after">
<field name="purchase" expand="1"/>
</xpath>
</data>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//group[@id='links']" position="inside">
<link icon="tryton-purchase" name="purchase.act_purchase_relate" empty="hide"/>
<link icon="tryton-purchase" name="purchase.act_purchase_line_relate" empty="hide"/>
</xpath>
<xpath expr="/form/notebook/page[@id='general']" position="after">
<page string="Supplier" id="supplier">
<label name="customer_code"/>
<field name="customer_code"/>
<newline/>
<label name="supplier_lead_time"/>
<field name="supplier_lead_time"/>
<label name="supplier_currency"/>
<field name="supplier_currency"/>
</page>
</xpath>
</data>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="code"/>
<field name="name"/>
<field name="purchase_price_uom"/>
<field name="cost_price_uom"/>
<field name="list_price_uom"/>
<field name="quantity"/>
<field name="forecast_quantity"/>
<field name="default_uom"/>
</tree>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form cursor="party">
<label name="template"/>
<field name="template" colspan="3"/>
<label name="product"/>
<field name="product" colspan="3"/>
<label name="company"/>
<field name="company" colspan="3"/>
<label name="party"/>
<field name="party"/>
<group id="sequence-active" col="-1" colspan="2">
<label name="sequence"/>
<field name="sequence"/>
<label name="active"/>
<field name="active"/>
</group>
<label name="name"/>
<field name="name"/>
<label name="code"/>
<field name="code"/>
<label name="lead_time"/>
<field name="lead_time"/>
<newline/>
<label name="currency"/>
<field name="currency"/>
<field name="prices" colspan="4"
view_ids="purchase.product_supplier_price_view_list_sequence"/>
<field name="unit" colspan="4" invisible="1"/>
</form>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<label name="product_supplier"/>
<field name="product_supplier"/>
<label name="sequence"/>
<field name="sequence"/>
<label name="quantity"/>
<field name="quantity" symbol="unit"/>
<label name="unit_price"/>
<field name="unit_price"/>
</form>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree sequence="sequence" editable="1">
<field name="product_supplier" expand="1"/>
<field name="quantity" symbol="unit"/>
<field name="unit_price" expand="1"/>
</tree>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="product_supplier" expand="1"/>
<field name="quantity" symbol="unit"/>
<field name="unit_price"/>
</tree>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="company" expand="1" optional="1"/>
<field name="template" expand="2"/>
<field name="product" expand="2"/>
<field name="party" expand="2"/>
<field name="name" expand="1"/>
<field name="code" expand="1"/>
</tree>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree sequence="sequence">
<field name="company" expand="1" optional="1"/>
<field name="template" expand="2"/>
<field name="product" expand="2"/>
<field name="party" expand="2"/>
<field name="name" expand="1"/>
<field name="code" expand="1"/>
</tree>

View File

@@ -0,0 +1,92 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form col="6">
<label name="party"/>
<field name="party"/>
<label name="contact"/>
<field name="contact"/>
<label name="number"/>
<field name="number"/>
<label name="invoice_party"/>
<field name="invoice_party"/>
<label name="invoice_address"/>
<field name="invoice_address" colspan="3"/>
<label name="description"/>
<field name="description" colspan="3"/>
<label name="reference"/>
<field name="reference"/>
<notebook colspan="6">
<page string="Purchase" id="purchase" col="6">
<label name="purchase_date"/>
<field name="purchase_date"/>
<label name="payment_term"/>
<field name="payment_term"/>
<label name="currency"/>
<field name="currency"/>
<label name="warehouse"/>
<field name="warehouse"/>
<label name="delivery_date"/>
<field name="delivery_date"/>
<label name="quotation_expire"/>
<field name="quotation_expire"/>
<field name="lines" colspan="6" view_ids="purchase.purchase_line_view_tree_sequence"/>
<group col="2" colspan="3" id="states" yfill="1">
<label name="invoice_state"/>
<field name="invoice_state"/>
<label name="shipment_state"/>
<field name="shipment_state"/>
<label name="state"/>
<field name="state"/>
</group>
<group col="2" colspan="3" id="amount" yfill="1">
<label name="untaxed_amount" xalign="1.0" xexpand="1" xfill="0"/>
<field name="untaxed_amount" xalign="1.0" xexpand="0"/>
<label name="tax_amount" xalign="1.0" xexpand="1" xfill="0"/>
<field name="tax_amount" xalign="1.0" xexpand="0"/>
<label name="total_amount" xalign="1.0" xexpand="1" xfill="0"/>
<field name="total_amount" xalign="1.0" xexpand="0"/>
</group>
</page>
<page string="Other Info" id="info">
<label name="company"/>
<field name="company"/>
<label name="origin"/>
<field name="origin"/>
<label name="invoice_method"/>
<field name="invoice_method"/>
<newline/>
<label name="quoted_by"/>
<field name="quoted_by"/>
<label name="confirmed_by"/>
<field name="confirmed_by"/>
<separator name="comment" colspan="4"/>
<field name="comment" colspan="4"/>
</page>
<page name="invoices_ignored" col="1">
<field name="invoices_ignored"/>
</page>
</notebook>
<group id="links" col="-1" colspan="3">
<link icon="tryton-shipment-in" name="purchase.act_shipment_form"/>
<link icon="tryton-account" name="purchase.act_invoice_form"/>
<link icon="tryton-shipment-out" name="purchase.act_return_form"/>
</group>
<group col="-1" colspan="3" id="buttons">
<button name="cancel" icon="tryton-cancel"/>
<button name="draft"/>
<button name="modify_header" icon="tryton-launch"/>
<button name="quote" icon="tryton-forward"/>
<button name="handle_invoice_exception" icon="tryton-forward"/>
<button name="handle_shipment_exception" icon="tryton-forward"/>
<button name="confirm" icon="tryton-ok"/>
<button name="process"/>
<button name="manual_invoice" icon="tryton-forward"/>
</group>
<field name="party_lang" invisible="1" colspan="6"/>
</form>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form cursor="product">
<label name="purchase"/>
<field name="purchase" colspan="3"/>
<label name="type"/>
<field name="type"/>
<label name="sequence"/>
<field name="sequence"/>
<notebook colspan="4">
<page string="General" id="general">
<label name="product"/>
<field name="product"
view_ids="purchase.product_view_list_purchase_line"/>
<label name="product_supplier"/>
<field name="product_supplier"/>
<label name="quantity"/>
<field name="quantity"/>
<label name="unit"/>
<field name="unit"/>
<label name="unit_price"/>
<field name="unit_price"/>
<label name="amount"/>
<field name="amount"/>
<label id="delivery_date" string="Delivery Date:"/>
<group id="delivery_date" col="-1">
<field name="delivery_date" xexpand="0"/>
<field name="delivery_date_edit" xexpand="0" xalign="0"/>
</group>
<separator name="description" colspan="4"/>
<field name="description" colspan="4"/>
</page>
<page string="Taxes" id="taxes">
<field name="taxes" colspan="4"/>
</page>
<page name="moves" col="1">
<field name="moves"/>
<field name="moves_ignored"/>
</page>
<page name="invoice_lines" col="1">
<field name="invoice_lines"/>
</page>
<page string="Notes" id="notes">
<separator name="note" colspan="4"/>
<field name="note" colspan="4"/>
</page>
</notebook>
</form>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="purchase" expand="1"/>
<field name="supplier" expand="1" optional="0"/>
<field name="purchase_date" optional="1"/>
<field name="type" optional="1"/>
<field name="product" expand="1" optional="0"/>
<field name="product_supplier" expand="1" optional="1"/>
<field name="summary" expand="1" optional="1"/>
<field name="actual_quantity" symbol="unit" optional="0"/>
<field name="quantity" symbol="unit" optional="0"/>
<field name="unit_price"/>
<field name="amount"/>
<field name="moves_progress" string="Shipping" widget="progressbar" optional="1"/>
<field name="invoice_progress" string="Invoicing" widget="progressbar" optional="1"/>
<field name="purchase_state"/>
</tree>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree sequence="sequence">
<field name="purchase" expand="1"/>
<field name="type" optional="1"/>
<field name="product" expand="1" optional="0"/>
<field name="product_supplier" expand="1" optional="1"/>
<field name="summary" expand="1" optional="1"/>
<field name="quantity" symbol="unit"/>
<field name="unit_price"/>
<field name="taxes" optional="0"/>
<field name="amount"/>
</tree>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<group id="dates" colspan="2" col="4">
<label name="from_date"/>
<field name="from_date"/>
<label name="to_date"/>
<field name="to_date"/>
</group>
<label name="period"/>
<field name="period"/>
<label name="company"/>
<field name="company"/>
<label name="warehouse"/>
<field name="warehouse"/>
</form>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<graph>
<x>
<field name="id"/>
</x>
<y>
<field name="expense"/>
</y>
</graph>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<graph>
<x>
<field name="id"/>
</x>
<y>
<field name="number"/>
</y>
</graph>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree keyword_open="1">
<field name="number" sum="1"/>
<field name="expense"/>
<field name="expense_trend" expand="1"/>
</tree>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<graph>
<x>
<field name="date"/>
</x>
<y>
<field name="expense"/>
</y>
</graph>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<graph>
<x>
<field name="date"/>
</x>
<y>
<field name="number"/>
</y>
</graph>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="date"/>
<field name="number" sum="1"/>
<field name="expense"/>
</tree>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//x/field[@name='id']" position="replace">
<field name="product"/>
</xpath>
</data>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//x/field[@name='id']" position="replace">
<field name="product"/>
</xpath>
</data>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='number']" position="before">
<field name="product" expand="1"/>
<field name="product_supplier" expand="1"/>
</xpath>
</data>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//x/field[@name='id']" position="replace">
<field name="supplier"/>
</xpath>
</data>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//x/field[@name='id']" position="replace">
<field name="supplier"/>
</xpath>
</data>

Some files were not shown because too many files have changed in this diff Show More