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

2
modules/sale/__init__.py Normal file
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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,121 @@
# 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 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
sale_invoice_method = fields.Selection(
'get_sale_invoice_methods', "Sale Invoice Method")
sale_shipment_method = fields.Selection(
'get_sale_shipment_methods', "Sale Shipment Method")
sale_quotation_validity = fields.TimeDelta(
"Sale Quotation Validity",
domain=['OR',
('sale_quotation_validity', '>=', dt.timedelta()),
('sale_quotation_validity', '=', None),
])
def get_sale_methods(field_name):
@classmethod
def func(cls):
pool = Pool()
Sale = pool.get('sale.sale')
return Sale.fields_get([field_name])[field_name]['selection']
return func
def default_func(field_name):
@classmethod
def default(cls, **pattern):
return getattr(
cls.multivalue_model(field_name),
'default_%s' % field_name, lambda: None)()
return default
class Configuration(
ModelSingleton, ModelSQL, ModelView, CompanyMultiValueMixin):
__name__ = 'sale.configuration'
sale_sequence = fields.MultiValue(fields.Many2One(
'ir.sequence', "Sale Sequence", required=True,
domain=[
('company', 'in',
[Eval('context', {}).get('company', -1), None]),
('sequence_type', '=', Id('sale', 'sequence_type_sale')),
]))
sale_quotation_validity = fields.MultiValue(sale_quotation_validity)
sale_invoice_method = fields.MultiValue(sale_invoice_method)
get_sale_invoice_methods = get_sale_methods('invoice_method')
sale_shipment_method = fields.MultiValue(sale_shipment_method)
get_sale_shipment_methods = get_sale_methods('shipment_method')
sale_process_after = fields.TimeDelta(
"Process Sale after",
domain=['OR',
('sale_process_after', '=', None),
('sale_process_after', '>=', TimeDelta()),
],
help="The grace period during which confirmed sale "
"can still be reset to draft.\n"
"Applied if a worker queue is activated.")
@classmethod
def multivalue_model(cls, field):
pool = Pool()
if field in {'sale_invoice_method', 'sale_shipment_method'}:
return pool.get('sale.configuration.sale_method')
if field == 'sale_sequence':
return pool.get('sale.configuration.sequence')
elif field == 'sale_quotation_validity':
return pool.get('sale.configuration.quotation')
return super().multivalue_model(field)
default_sale_sequence = default_func('sale_sequence')
default_sale_invoice_method = default_func('sale_invoice_method')
default_sale_shipment_method = default_func('sale_shipment_method')
class ConfigurationSequence(ModelSQL, CompanyValueMixin):
__name__ = 'sale.configuration.sequence'
sale_sequence = fields.Many2One(
'ir.sequence', "Sale Sequence", required=True,
domain=[
('company', 'in', [Eval('company', -1), None]),
('sequence_type', '=', Id('sale', 'sequence_type_sale')),
])
@classmethod
def default_sale_sequence(cls):
pool = Pool()
ModelData = pool.get('ir.model.data')
try:
return ModelData.get_id('sale', 'sequence_sale')
except KeyError:
return None
class ConfigurationSaleMethod(ModelSQL, ValueMixin):
__name__ = 'sale.configuration.sale_method'
sale_invoice_method = sale_invoice_method
get_sale_invoice_methods = get_sale_methods('invoice_method')
sale_shipment_method = sale_shipment_method
get_sale_shipment_methods = get_sale_methods('shipment_method')
@classmethod
def default_sale_invoice_method(cls):
return 'order'
@classmethod
def default_sale_shipment_method(cls):
return 'order'
class ConfigurationQuotation(ModelSQL, ValueMixin):
__name__ = 'sale.configuration.quotation'
sale_quotation_validity = sale_quotation_validity

View File

@@ -0,0 +1,56 @@
<?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
name="Configuration"
parent="menu_sale"
sequence="0"
id="menu_configuration"
icon="tryton-settings"/>
<record model="ir.ui.menu-res.group"
id="menu_configuration_group_sale_admin">
<field name="menu" ref="menu_configuration"/>
<field name="group" ref="group_sale_admin"/>
</record>
<record model="ir.ui.view" id="sale_configuration_view_form">
<field name="model">sale.configuration</field>
<field name="type">form</field>
<field name="name">configuration_form</field>
</record>
<record model="ir.action.act_window" id="act_sale_configuration_form">
<field name="name">Configuration</field>
<field name="res_model">sale.configuration</field>
</record>
<record model="ir.action.act_window.view"
id="act_sale_configuration_view1">
<field name="sequence" eval="1"/>
<field name="view" ref="sale_configuration_view_form"/>
<field name="act_window" ref="act_sale_configuration_form"/>
</record>
<menuitem
parent="menu_configuration"
action="act_sale_configuration_form"
sequence="10"
id="menu_sale_configuration"
icon="tryton-list"/>
<record model="ir.model.access" id="access_sale_configuration">
<field name="model">sale.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_sale_configuration_sale_admin">
<field name="model">sale.configuration</field>
<field name="group" ref="group_sale_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,25 @@
# 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 SaleValidationError(ValidationError):
pass
class SaleQuotationError(ValidationError):
pass
class SaleConfirmError(UserError):
pass
class SaleMoveQuantity(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="M0 0h24v24H0z" fill="none"/>
<path d="M20 4H4v2h16V4zm1 10v-2l-1-5H4l-1 5v2h1v6h10v-6h4v6h2v-6h1zm-9 4H6v-4h6v4z"/>
</svg>

After

Width:  |  Height:  |  Size: 224 B

175
modules/sale/invoice.py Normal file
View File

@@ -0,0 +1,175 @@
# 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 Workflow, fields
from trytond.model.exceptions import AccessError
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.transaction import Transaction, without_check_access
def process_sale(func):
@wraps(func)
def wrapper(cls, invoices):
pool = Pool()
Sale = pool.get('sale.sale')
transaction = Transaction()
context = transaction.context
with without_check_access():
sales = set(s for i in cls.browse(invoices) for s in i.sales)
result = func(cls, invoices)
if sales:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Sale.__queue__.process(sales)
return result
return wrapper
class Invoice(metaclass=PoolMeta):
__name__ = 'account.invoice'
sale_exception_state = fields.Function(fields.Selection([
('', ''),
('ignored', 'Ignored'),
('recreated', 'Recreated'),
], 'Exception State'), 'get_sale_exception_state')
sales = fields.Function(fields.Many2Many(
'sale.sale', None, None, "Sales"),
'get_sales', searcher='search_sales')
def get_sale_exception_state(self, name):
sales = self.sales
recreated = tuple(i for p in sales for i in p.invoices_recreated)
ignored = tuple(i for p in sales for i in p.invoices_ignored)
if self in recreated:
return 'recreated'
elif self in ignored:
return 'ignored'
return ''
def get_sales(self, name):
pool = Pool()
SaleLine = pool.get('sale.line')
sales = set()
for line in self.lines:
if isinstance(line.origin, SaleLine):
sales.add(line.origin.sale.id)
return list(sales)
@classmethod
def search_sales(cls, name, clause):
return [('lines.origin.sale' + clause[0][len(name):],
*clause[1:3], 'sale.line', *clause[3:])]
def get_tax_identifier(self, pattern=None):
if self.sales:
pattern = pattern.copy() if pattern is not None else {}
shipment_countries = {
s.shipment_address.country for s in self.sales
if s.shipment_address and s.shipment_address.country}
if len(shipment_countries) == 1:
shipment_country, = shipment_countries
pattern.setdefault('country', shipment_country.id)
return super().get_tax_identifier(pattern=pattern)
@classmethod
@process_sale
def on_delete(cls, invoices):
return super().on_delete(invoices)
@classmethod
@process_sale
def _post(cls, invoices):
super()._post(invoices)
@classmethod
@process_sale
def paid(cls, invoices):
super().paid(invoices)
@classmethod
@process_sale
def cancel(cls, invoices):
super().cancel(invoices)
@classmethod
@Workflow.transition('draft')
def draft(cls, invoices):
for invoice in invoices:
if invoice.sales and invoice.state == 'cancelled':
raise AccessError(
gettext('sale.msg_sale_invoice_reset_draft',
invoice=invoice.rec_name))
return super().draft(invoices)
class Line(metaclass=PoolMeta):
__name__ = 'account.invoice.line'
@classmethod
def __setup__(cls):
super().__setup__()
if not cls.origin.domain:
cls.origin.domain = {}
cls.origin.domain['sale.line'] = [
('type', '=', Eval('type')),
]
@fields.depends('origin')
def on_change_with_product_uom_category(self, name=None):
pool = Pool()
SaleLine = pool.get('sale.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 ship.
# Use getattr as reference field can have negative id
if (isinstance(self.origin, SaleLine)
and getattr(self.origin, 'unit', None)):
category = self.origin.unit.category
return category
def get_warehouse(self, name):
pool = Pool()
SaleLine = pool.get('sale.line')
warehouse = super().get_warehouse(name)
if (not warehouse
and isinstance(self.origin, SaleLine)
and self.origin.warehouse):
warehouse = self.origin.warehouse.id
return warehouse
@property
def origin_name(self):
pool = Pool()
SaleLine = pool.get('sale.line')
name = super().origin_name
if isinstance(self.origin, SaleLine) and self.origin.id >= 0:
name = self.origin.sale.rec_name
return name
@classmethod
def _get_origin(cls):
models = super()._get_origin()
models.append('sale.line')
return models
@classmethod
def on_delete(cls, lines):
pool = Pool()
Sale = pool.get('sale.sale')
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)
sales = set(s for i in invoices for s in i.sales)
if sales:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Sale.__queue__.process(sales)
return super().on_delete(lines)

53
modules/sale/invoice.xml Normal file
View File

@@ -0,0 +1,53 @@
<?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="domain"
eval="[('sales.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">sale.sale,-1</field>
<field name="action" ref="act_invoice_form"/>
</record>
<record model="ir.model.access" id="access_invoice_sale">
<field name="model">account.invoice</field>
<field name="group" ref="group_sale"/>
<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', []), 'sale.line')]"
pyson="1"/>
</record>
<record model="ir.action.keyword" id="act_invoice_line_form_keyword1">
<field name="keyword">form_relate</field>
<field name="model">sale.line,-1</field>
<field name="action" ref="act_invoice_line_form"/>
</record>
<record model="ir.model.access" id="access_invoice_line_sale">
<field name="model">account.invoice.line</field>
<field name="group" ref="group_sale"/>
<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>

16
modules/sale/ir.py Normal file
View File

@@ -0,0 +1,16 @@
# 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.pool import PoolMeta
class Cron(metaclass=PoolMeta):
__name__ = 'ir.cron'
@classmethod
def __setup__(cls):
super().__setup__()
cls.method.selection.extend([
('sale.sale|cancel_expired_quotation',
"Cancel Expired Sale Quotation"),
])

1871
modules/sale/locale/bg.po Normal file

File diff suppressed because it is too large Load Diff

1737
modules/sale/locale/ca.po Normal file

File diff suppressed because it is too large Load Diff

1809
modules/sale/locale/cs.po Normal file

File diff suppressed because it is too large Load Diff

1743
modules/sale/locale/de.po Normal file

File diff suppressed because it is too large Load Diff

1737
modules/sale/locale/es.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1868
modules/sale/locale/et.po Normal file

File diff suppressed because it is too large Load Diff

1881
modules/sale/locale/fa.po Normal file

File diff suppressed because it is too large Load Diff

1806
modules/sale/locale/fi.po Normal file

File diff suppressed because it is too large Load Diff

1734
modules/sale/locale/fr.po Normal file

File diff suppressed because it is too large Load Diff

1839
modules/sale/locale/hu.po Normal file

File diff suppressed because it is too large Load Diff

1812
modules/sale/locale/id.po Normal file

File diff suppressed because it is too large Load Diff

1864
modules/sale/locale/it.po Normal file

File diff suppressed because it is too large Load Diff

1905
modules/sale/locale/lo.po Normal file

File diff suppressed because it is too large Load Diff

1864
modules/sale/locale/lt.po Normal file

File diff suppressed because it is too large Load Diff

1737
modules/sale/locale/nl.po Normal file

File diff suppressed because it is too large Load Diff

1793
modules/sale/locale/pl.po Normal file

File diff suppressed because it is too large Load Diff

1735
modules/sale/locale/pt.po Normal file

File diff suppressed because it is too large Load Diff

1796
modules/sale/locale/ro.po Normal file

File diff suppressed because it is too large Load Diff

1872
modules/sale/locale/ru.po Normal file

File diff suppressed because it is too large Load Diff

1853
modules/sale/locale/sl.po Normal file

File diff suppressed because it is too large Load Diff

1805
modules/sale/locale/tr.po Normal file

File diff suppressed because it is too large Load Diff

1696
modules/sale/locale/uk.po Normal file

File diff suppressed because it is too large Load Diff

1826
modules/sale/locale/zh_CN.po Normal file

File diff suppressed because it is too large Load Diff

79
modules/sale/message.xml Normal file
View File

@@ -0,0 +1,79 @@
<?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_erase_party_pending_sale">
<field name="text">You cannot erase party "%(party)s" while they have pending sales with company "%(company)s".</field>
</record>
<record model="ir.message" id="msg_sale_invoice_reset_draft">
<field name="text">You cannot reset invoice "%(invoice)s" to draft because it was generated by a sale.</field>
</record>
<record model="ir.message" id="msg_sale_move_reset_draft">
<field name="text">You cannot reset move "%(move)s" to draft because it was generated by a sale.</field>
</record>
<record model="ir.message" id="msg_sale_invalid_method">
<field name="text">You cannot use together invoice "%(invoice_method)s" and shipment "%(shipment_method)s" on sale "%(sale)s".</field>
</record>
<record model="ir.message" id="msg_sale_invoice_address_required_for_quotation">
<field name="text">To get a quote for sale "%(sale)s" you must enter an invoice address.</field>
</record>
<record model="ir.message" id="msg_sale_shipment_address_required_for_quotation">
<field name="text">To get a quote for sale "%(sale)s" you must enter a shipment address.</field>
</record>
<record model="ir.message" id="msg_sale_warehouse_required_for_quotation">
<field name="text">To get a quote for sale "%(sale)s" you must enter a warehouse for the line "%(line)s".</field>
</record>
<record model="ir.message" id="msg_sale_delete_cancel">
<field name="text">To delete sale "%(sale)s" you must cancel it.</field>
</record>
<record model="ir.message" id="msg_sale_customer_location_required">
<field name="text">To process sale "%(sale)s" you must set a customer location on party "%(party)s".</field>
</record>
<record model="ir.message" id="msg_sale_product_missing_account_revenue">
<field name="text">To invoice sale "%(sale)s" you must define an account revenue for product "%(product)s".</field>
</record>
<record model="ir.message" id="msg_sale_missing_account_revenue">
<field name="text">To invoice sale "%(sale)s" you must configure a default account revenue.</field>
</record>
<record model="ir.message" id="msg_sale_line_delete_cancel_draft">
<field name="text">To delete line "%(line)s" you must cancel or reset to draft sale "%(sale)s".</field>
</record>
<record model="ir.message" id="msg_sale_line_move_quantity">
<field name="text">The sale line "%(line)s" is moving %(extra)s in addition to the %(quantity)s ordered.</field>
</record>
<record model="ir.message" id="msg_sale_modify_header_draft">
<field name="text">To modify the header of sale "%(sale)s", it must be in "draft" state.</field>
</record>
<record model="ir.message" id="msg_sale_line_create_draft">
<field name="text">You cannot add lines to sale "%(sale)s" because it is no longer in a draft state.</field>
</record>
<record model="ir.message" id="msg_sale_reporting_company">
<field name="text">Company</field>
</record>
<record model="ir.message" id="msg_sale_reporting_number">
<field name="text">#</field>
</record>
<record model="ir.message" id="msg_sale_reporting_number_help">
<field name="text">Number of sales</field>
</record>
<record model="ir.message" id="msg_sale_reporting_revenue">
<field name="text">Revenue</field>
</record>
<record model="ir.message" id="msg_sale_reporting_revenue_trend">
<field name="text">Revenue Trend</field>
</record>
<record model="ir.message" id="msg_sale_reporting_currency">
<field name="text">Currency</field>
</record>
<record model="ir.message" id="msg_sale_reporting_date">
<field name="text">Date</field>
</record>
<record model="ir.message" id="msg_sale_reporting_time_series">
<field name="text">Time Series</field>
</record>
<record model="ir.message" id="msg_sale_line_tax_unique">
<field name="text">A tax can be added only once to a sale line.</field>
</record>
</data>
</tryton>

123
modules/sale/party.py Normal file
View File

@@ -0,0 +1,123 @@
# 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
from trytond.transaction import Transaction
customer_currency = fields.Many2One(
'currency.currency', "Customer Currency", ondelete='RESTRICT',
help="Default currency for sales to this party.")
def get_sale_methods(field_name):
@classmethod
def func(cls):
pool = Pool()
Sale = pool.get('sale.sale')
return Sale.fields_get([field_name])[field_name]['selection'] + [
(None, '')]
return func
class Party(CompanyMultiValueMixin, metaclass=PoolMeta):
__name__ = 'party.party'
sale_invoice_method = fields.MultiValue(fields.Selection(
'get_sale_invoice_method', "Invoice Method",
help="The default sale invoice method for the customer.\n"
"Leave empty to use the default value from the configuration."))
sale_shipment_method = fields.MultiValue(fields.Selection(
'get_sale_shipment_method', "Shipment Method",
help="The default sale shipment method for the customer.\n"
"Leave empty to use the default value from the configuration."))
sale_methods = fields.One2Many(
'party.party.sale_method', 'party', "Sale Methods")
customer_currency = fields.MultiValue(customer_currency)
customer_currencies = fields.One2Many(
'party.party.customer_currency', 'party', "Customer Currencies")
@classmethod
def multivalue_model(cls, field):
pool = Pool()
if field in {'sale_invoice_method', 'sale_shipment_method'}:
return pool.get('party.party.sale_method')
return super().multivalue_model(field)
get_sale_invoice_method = get_sale_methods('invoice_method')
get_sale_shipment_method = get_sale_methods('shipment_method')
@classmethod
def copy(cls, parties, default=None):
default = default.copy() if default else {}
if Transaction().check_access:
fields = [
'sale_methods', 'sale_invoice_method', 'sale_shipment_method']
default_values = cls.default_get(fields, with_rec_name=False)
for fname in fields:
default.setdefault(fname, default_values.get(fname))
return super().copy(parties, default=default)
class PartySaleMethod(ModelSQL, CompanyValueMixin):
__name__ = 'party.party.sale_method'
party = fields.Many2One(
'party.party', "Party", ondelete='CASCADE',
context={
'company': Eval('company', -1),
},
depends={'company'})
sale_invoice_method = fields.Selection(
'get_sale_invoice_method', "Sale Invoice Method")
sale_shipment_method = fields.Selection(
'get_sale_shipment_method', "Sale Shipment Method")
get_sale_invoice_method = get_sale_methods('invoice_method')
get_sale_shipment_method = get_sale_methods('shipment_method')
class PartyCustomerCurrency(ModelSQL, ValueMixin):
__name__ = 'party.party.customer_currency'
party = fields.Many2One(
'party.party', "Party", ondelete='CASCADE')
customer_currency = customer_currency
class Replace(metaclass=PoolMeta):
__name__ = 'party.replace'
@classmethod
def fields_to_replace(cls):
return super().fields_to_replace() + [
('sale.sale', 'party'),
('sale.sale', 'invoice_party'),
('sale.sale', 'shipment_party'),
]
class Erase(metaclass=PoolMeta):
__name__ = 'party.erase'
def check_erase_company(self, party, company):
pool = Pool()
Sale = pool.get('sale.sale')
super().check_erase_company(party, company)
sales = Sale.search([
['OR',
('party', '=', party.id),
('shipment_party', '=', party.id),
],
('company', '=', company.id),
('state', 'not in', ['done', 'cancelled']),
])
if sales:
raise EraseError(
gettext('sale.msg_erase_party_pending_sale',
party=party.rec_name,
company=company.rec_name))

54
modules/sale/party.xml Normal file
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.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_sale_invoice_method">
<field name="model">party.party</field>
<field name="field">sale_invoice_method</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
</record>
<record model="ir.model.field.access" id="access_party_sale_invoice_method_group_sale">
<field name="model">party.party</field>
<field name="field">sale_invoice_method</field>
<field name="group" ref="group_sale"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
</record>
<record model="ir.model.field.access" id="access_party_sale_shipment_method">
<field name="model">party.party</field>
<field name="field">sale_shipment_method</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
</record>
<record model="ir.model.field.access" id="access_party_sale_shipment_method_group_sale">
<field name="model">party.party</field>
<field name="field">sale_shipment_method</field>
<field name="group" ref="group_sale"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
</record>
<record model="ir.model.field.access" id="access_party_customer_currency">
<field name="model">party.party</field>
<field name="field">customer_currency</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
</record>
<record model="ir.model.field.access" id="access_party_customer_currency_group_sale">
<field name="model">party.party</field>
<field name="field">customer_currency</field>
<field name="group" ref="group_sale"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
</record>
</data>
</tryton>

281
modules/sale/product.py Normal file
View File

@@ -0,0 +1,281 @@
# 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 sql import Null
from trytond.model import ModelSQL, ModelView, ValueMixin, fields
from trytond.modules.currency.fields import Monetary
from trytond.modules.product import price_digits, round_price
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval, TimeDelta
from trytond.transaction import Transaction
default_lead_time = fields.TimeDelta(
"Default Lead Time",
domain=['OR',
('default_lead_time', '=', None),
('default_lead_time', '>=', TimeDelta()),
],
help="The time from confirming the sales order to sending the "
"products.\n"
"Used for products without a lead time.")
class Configuration(metaclass=PoolMeta):
__name__ = 'product.configuration'
default_lead_time = fields.MultiValue(default_lead_time)
class DefaultLeadTime(ModelSQL, ValueMixin):
__name__ = 'product.configuration.default_lead_time'
default_lead_time = default_lead_time
class Template(metaclass=PoolMeta):
__name__ = 'product.template'
salable = fields.Boolean("Salable")
sale_uom = fields.Many2One(
'product.uom', "Sale UoM",
states={
'invisible': ~Eval('salable', False),
'required': Eval('salable', False),
},
domain=[
('category', '=', Eval('default_uom_category', -1)),
],
help="The default Unit of Measure for sales.")
lead_time = fields.MultiValue(fields.TimeDelta(
"Lead Time",
domain=['OR',
('lead_time', '=', None),
('lead_time', '>=', TimeDelta()),
],
states={
'invisible': ~Eval('salable', False),
},
help="The time from confirming the sales order to sending the "
"products.\n"
"If empty the default lead time from the configuration is used."))
lead_times = fields.One2Many(
'product.lead_time', 'template', "Lead Times")
@classmethod
def multivalue_model(cls, field):
pool = Pool()
if field == 'lead_time':
return pool.get('product.lead_time')
return super().multivalue_model(field)
@fields.depends('default_uom', 'sale_uom', 'salable')
def on_change_default_uom(self):
try:
super().on_change_default_uom()
except AttributeError:
pass
if self.default_uom:
if self.sale_uom:
if self.default_uom.category != self.sale_uom.category:
self.sale_uom = self.default_uom
else:
self.sale_uom = self.default_uom
@classmethod
def view_attributes(cls):
return super().view_attributes() + [
('//page[@id="customers"]', 'states', {
'invisible': ~Eval('salable'),
})]
class ProductLeadTime(ModelSQL, ValueMixin):
__name__ = 'product.lead_time'
template = fields.Many2One(
'product.template', "Template", ondelete='CASCADE')
lead_time = fields.TimeDelta(
"Lead Time",
domain=['OR',
('lead_time', '=', None),
('lead_time', '>=', TimeDelta()),
])
@classmethod
def __register__(cls, module_name):
pool = Pool()
Template = pool.get('product.template')
template = Template.__table__()
table = cls.__table__()
super().__register__(module_name)
cursor = Transaction().connection.cursor()
template_h = Template.__table_handler__(module_name)
if template_h.column_exist('lead_time'):
cursor.execute(*table.insert(
columns=[table.template, table.lead_time],
values=template.select(
template.id, template.lead_time,
where=template.lead_time != Null)))
template_h.drop_column('lead_time')
class Product(metaclass=PoolMeta):
__name__ = 'product.product'
sale_price_uom = fields.Function(Monetary(
"Sale Price", digits=price_digits), 'get_sale_price_uom')
@classmethod
def get_sale_price_uom(cls, products, name):
quantity = Transaction().context.get('quantity') or 0
return cls.get_sale_price(products, quantity=quantity)
def _get_sale_unit_price(self, quantity=0):
return self.list_price_used
@classmethod
def get_sale_price(cls, products, quantity=0):
'''
Return the sale price for products and quantity.
It uses if exists from the context:
uom: the unit of measure or the sale uom of the product
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')
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.get('currency'))
company = None
if context.get('company'):
company = Company(context['company'])
date = context.get('sale_date') or Date.today()
for product in products:
unit_price = product._get_sale_unit_price(quantity=quantity)
if unit_price is not None:
if uom and product.default_uom.category == uom.category:
unit_price = Uom.compute_price(
product.default_uom, unit_price, uom)
elif product.sale_uom:
unit_price = Uom.compute_price(
product.default_uom, unit_price, product.sale_uom)
if currency and company and unit_price is not None:
if company.currency != currency:
with Transaction().set_context(date=date):
unit_price = Currency.compute(
company.currency, unit_price,
currency, round=False)
if unit_price is not None:
unit_price = round_price(unit_price)
prices[product.id] = unit_price
return prices
@property
def lead_time_used(self):
pool = Pool()
Configuration = pool.get('product.configuration')
if self.lead_time is None:
with Transaction().set_context(self._context):
config = Configuration(1)
return config.get_multivalue('default_lead_time')
else:
return self.lead_time
def compute_shipping_date(self, date=None):
'''
Compute the shipping date at the given date
'''
Date = Pool().get('ir.date')
if not date:
with Transaction().set_context(context=self._context):
date = Date.today()
lead_time = self.lead_time_used
if lead_time is None:
return datetime.date.max
return date + lead_time
class SaleContext(ModelView):
__name__ = 'product.sale.context'
locations = fields.Many2Many(
'stock.location', None, None, "Warehouses",
domain=[('type', '=', 'warehouse')])
company = fields.Many2One('company.company', "Company")
currency = fields.Many2One('currency.currency', "Currency")
customer = fields.Many2One(
'party.party', "Customer",
context={
'company': Eval('company', -1),
},
depends={'company'})
sale_date = fields.Date("Sale Date")
quantity = fields.Float("Quantity")
stock_date_end = fields.Function(
fields.Date("Stock End Date"),
'on_change_with_stock_date_end')
@classmethod
def default_locations(cls):
pool = Pool()
Location = pool.get('stock.location')
context = Transaction().context
locations = context.get('locations')
if locations is None:
locations = []
warehouse = Location.get_default_warehouse()
if warehouse:
locations.append(warehouse)
return locations
@classmethod
def default_company(cls):
return Transaction().context.get('company')
@classmethod
def default_currency(cls):
pool = Pool()
Company = pool.get('company.company')
context = Transaction().context
currency = context.get('currency')
if currency is None:
company_id = cls.default_company()
if company_id is not None and company_id >= 0:
company = Company(company_id)
currency = company.currency.id
return currency
@classmethod
def default_customer(cls):
return Transaction().context.get('customer')
@classmethod
def default_sale_date(cls):
return Transaction().context.get('sale_date')
@classmethod
def default_quantity(cls):
return Transaction().context.get('quantity')
@fields.depends('sale_date')
def on_change_with_stock_date_end(self, name=None):
return self.sale_date

62
modules/sale/product.xml Normal file
View File

@@ -0,0 +1,62 @@
<?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_sale_line">
<field name="model">product.product</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">product_list_sale_line</field>
</record>
<record model="ir.action.act_window" id="act_product">
<field name="name">Products</field>
<field name="res_model">product.product</field>
<field name="domain" eval="[('salable', '=', True)]" pyson="1"/>
<field name="context_model">product.sale.context</field>
<field
name="context"
eval="{'stock_skip_warehouse': True, 'with_childs': True}"
pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_product_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="product_view_list_sale_line"/>
<field name="act_window" ref="act_product"/>
</record>
<record model="ir.action.act_window.view" id="act_product_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="product.product_view_form"/>
<field name="act_window" ref="act_product"/>
</record>
<menuitem
parent="menu_sale"
action="act_product"
sequence="50"
id="menu_product"/>
<record model="ir.ui.view" id="product_sale_context_view_form">
<field name="model">product.sale.context</field>
<field name="type">form</field>
<field name="name">product_sale_context_form</field>
</record>
<record model="ir.ui.view" id="product_configuration_view_form">
<field name="model">product.configuration</field>
<field name="inherit" ref="product.product_configuration_view_form"/>
<field name="name">product_configuration_form</field>
</record>
</data>
</tryton>

964
modules/sale/sale.fodt Normal file
View File

@@ -0,0 +1,964 @@
<?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/24.2.7.2$Linux_X86_64 LibreOffice_project/420$Build-2</meta:generator><meta:creation-date>2008-06-07T15:28:22</meta:creation-date><dc:date>2009-01-10T16:03:33</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="7" meta:paragraph-count="99" meta:word-count="236" meta:character-count="2719" meta:non-whitespace-character-count="2581"/><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">39582</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">48103</config:config-item>
<config:config-item config:name="ViewAreaHeight" config:type="long">22915</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">5339</config:config-item>
<config:config-item config:name="ViewTop" config:type="long">46011</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">39582</config:config-item>
<config:config-item config:name="VisibleRight" config:type="long">48101</config:config-item>
<config:config-item config:name="VisibleBottom" config:type="long">62495</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="PrintProspectRTL" 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="PrintDrawings" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintFaxName" config:type="string"/>
<config:config-item config:name="PrintReversed" 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="PrintHiddenText" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintEmptyPages" 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="NoNumberingShowFollowBy" 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="ImagePreferredDPI" config:type="int">0</config:config-item>
<config:config-item config:name="FootnoteInColumnToPageEnd" config:type="boolean">true</config:config-item>
<config:config-item config:name="GutterAtTop" 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="FrameAutowidthWithMorePara" config:type="boolean">false</config:config-item>
<config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">true</config:config-item>
<config:config-item config:name="SurroundTextWrapSmall" 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="TreatSingleColumnBreakAsPageBreak" 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="AutoFirstLineIndentDisregardLineSpace" 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="EmbedAsianScriptFonts" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintTextPlaceholder" config:type="boolean">false</config:config-item>
<config:config-item config:name="ApplyTextAttrToEmptyLineAtEndOfParagraph" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmbedSystemFonts" 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="DisableOffPagePositioning" 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="TabOverflow" 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="AddVerticalFrameOffsets" 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="AddFrameOffsets" 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="TableRowKeep" config:type="boolean">false</config:config-item>
<config:config-item config:name="ApplyParagraphMarkFormatToNumbering" 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="JustifyLinesWithShrinking" config:type="boolean">false</config:config-item>
<config:config-item config:name="RsidRoot" config:type="int">204959</config:config-item>
<config:config-item config:name="PrintProspect" config:type="boolean">false</config:config-item>
<config:config-item config:name="CollapseEmptyCellPara" 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="CurrentDatabaseCommand" config:type="string"/>
<config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/>
<config:config-item config:name="SaveThumbnail" config:type="boolean">true</config:config-item>
<config:config-item config:name="EmbeddedDatabaseName" config:type="string"/>
<config:config-item config:name="UnbreakableNumberings" 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="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintLeftPages" config:type="boolean">true</config:config-item>
<config:config-item config:name="AddParaTableSpacing" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrinterPaperFromSetup" 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="ChartAutoUpdate" 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="UseOldNumbering" 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="FieldAutoUpdate" config:type="boolean">true</config:config-item>
<config:config-item config:name="PropLineSpacingShrinksFirstLine" 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="PrintRightPages" config:type="boolean">true</config:config-item>
<config:config-item config:name="DoNotCaptureDrawObjsOnPage" 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="HeaderSpacingBelowLastPara" config:type="boolean">false</config:config-item>
<config:config-item config:name="SaveVersionOnClose" 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="MathBaselineAlignment" config:type="boolean">false</config:config-item>
<config:config-item config:name="AlignTabStopPosition" config:type="boolean">true</config:config-item>
<config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrinterName" config:type="string"/>
<config:config-item config:name="AddParaLineSpacingToTableCells" 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="DoNotJustifyLinesWithManualBreak" config:type="boolean">false</config:config-item>
<config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
<config:config-item config:name="IsLabelDocument" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmbedLatinScriptFonts" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrinterSetup" config:type="base64Binary"/>
<config:config-item config:name="UseVariableWidthNBSP" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmbedOnlyUsedFonts" 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="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item>
<config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item>
<config:config-item config:name="ProtectFields" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddExternalLeading" config:type="boolean">true</config:config-item>
<config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item>
<config:config-item config:name="UseFormerObjectPositioning" 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="UseFormerTextWrapping" config:type="boolean">false</config:config-item>
<config:config-item config:name="ConsiderTextWrapOnObjPos" 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="IgnoreFirstLineIndentInNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="NoGapAfterNoteNumber" 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="DoNotResetParaAttrsForNumFont" 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="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item>
<config:config-item config:name="DropCapPunctuation" 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="RedlineProtectionKey" config:type="base64Binary"/>
<config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item>
<config:config-item config:name="Rsid" config:type="int">2203069</config:config-item>
<config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item>
<config:config-item config:name="ProtectForm" 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="SmallCapsPercentage66" 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>
<draw:gradient draw:name="gradient" draw:style="linear" draw:start-color="#000000" draw:end-color="#ffffff" draw:start-intensity="100%" draw:end-intensity="100%" draw:angle="0deg" draw:border="0%">
<loext:gradient-stop svg:offset="0" loext:color-type="rgb" loext:color-value="#000000"/>
<loext:gradient-stop svg:offset="1" loext:color-type="rgb" loext:color-value="#ffffff"/></draw:gradient>
<draw:hatch draw:name="hatch" draw:style="single" draw:color="#3465a4" draw:distance="0.02cm" draw:rotation="0"/>
<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" draw:fill-color="#99ccff" 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="00066dfa"/>
</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="00066dfa"/>
</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="00066dfa" style:font-size-asian="12pt" style:font-size-complex="12pt"/>
</style:style>
<style:style style:name="P4" 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="001be9bb" style:font-size-asian="12pt" style:font-size-complex="12pt"/>
</style:style>
<style:style style:name="P5" 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="P6" 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="00066dfa"/>
</style:style>
<style:style style:name="P7" 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="00066dfa"/>
</style:style>
<style:style style:name="P8" 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="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="00066dfa" style:font-size-asian="12pt" style:font-size-complex="12pt"/>
</style:style>
<style:style style:name="P10" 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="001be9bb" style:font-size-asian="12pt" style:font-size-complex="12pt"/>
</style:style>
<style:style style:name="P11" 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="P12" 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="P13" 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="P14" 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="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="0011a01d"/>
</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="0011a01d"/>
</style:style>
<style:style style:name="P17" 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="000ce218"/>
</style:style>
<style:style style:name="P18" 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="P19" 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="P20" 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="0008048e"/>
</style:style>
<style:style style:name="P21" 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="P22" 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" officeooo:paragraph-rsid="001ac103"/>
</style:style>
<style:style style:name="P23" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:text-properties officeooo:rsid="0007edf0" officeooo:paragraph-rsid="0007edf0"/>
</style:style>
<style:style style:name="P24" style:family="paragraph" style:parent-style-name="Text_20_body" style:master-page-name="">
<loext:graphic-properties draw:fill-gradient-name="gradient" draw:fill-hatch-name="hatch"/>
<style:paragraph-properties style:page-number="auto" fo:keep-with-next="auto"/>
<style:text-properties officeooo:rsid="0007edf0" officeooo:paragraph-rsid="0007edf0"/>
</style:style>
<style:style style:name="P25" 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="P26" style:family="paragraph" style:parent-style-name="Text_20_body" style:master-page-name="">
<loext:graphic-properties draw:fill-gradient-name="gradient" draw:fill-hatch-name="hatch"/>
<style:paragraph-properties style:page-number="auto" fo:keep-with-next="always"/>
</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-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-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="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="0014b27d"/>
</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="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="0011a01d" style:font-size-asian="5.25pt" style:font-size-complex="6pt"/>
</style:style>
<style:style style:name="P32" style:family="paragraph" style:parent-style-name="Text_20_body" style:master-page-name="">
<loext:graphic-properties draw:fill-gradient-name="gradient" draw:fill-hatch-name="hatch"/>
<style:paragraph-properties style:page-number="auto" fo:keep-with-next="auto"/>
<style:text-properties officeooo:paragraph-rsid="001ac103"/>
</style:style>
<style:style style:name="P33" style:family="paragraph" style:parent-style-name="Text_20_body" style:master-page-name="">
<loext:graphic-properties draw:fill-gradient-name="gradient" draw:fill-hatch-name="hatch"/>
<style:paragraph-properties style:page-number="auto" fo:break-before="column" fo:keep-with-next="auto"/>
<style:text-properties officeooo:paragraph-rsid="001ac103"/>
</style:style>
<style:style style:name="P34" style:family="paragraph" style:parent-style-name="Text_20_body">
<loext:graphic-properties draw:fill-gradient-name="gradient" draw:fill-hatch-name="hatch"/>
<style:text-properties officeooo:paragraph-rsid="001ac103"/>
</style:style>
<style:style style:name="P35" style:family="paragraph" style:parent-style-name="Text_20_body">
<loext:graphic-properties draw:fill-gradient-name="gradient" draw:fill-hatch-name="hatch"/>
<style:paragraph-properties fo:margin-left="1cm" fo:margin-right="0cm" fo:text-indent="0cm" style:auto-text-indent="false" fo:keep-with-next="auto"/>
<style:text-properties officeooo:rsid="001ac103" officeooo:paragraph-rsid="001ac103"/>
</style:style>
<style:style style:name="P36" style:family="paragraph" style:parent-style-name="Text_20_body" style:master-page-name="">
<loext:graphic-properties draw:fill-gradient-name="gradient" draw:fill-hatch-name="hatch"/>
<style:paragraph-properties style:page-number="auto" fo:break-before="column" fo:keep-with-next="auto"/>
<style:text-properties officeooo:rsid="001ac103" officeooo:paragraph-rsid="001ac103"/>
</style:style>
<style:style style:name="P37" style:family="paragraph" style:parent-style-name="Text_20_body">
<loext:graphic-properties draw:fill-gradient-name="gradient" draw:fill-hatch-name="hatch"/>
<style:text-properties officeooo:rsid="002014be" officeooo:paragraph-rsid="002014be"/>
</style:style>
<style:style style:name="T1" style:family="text">
<style:text-properties officeooo:rsid="001ac103"/>
</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="P4"><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="6">
<draw:text-box fo:min-height="3cm">
<text:p text:style-name="P5"/>
</draw:text-box>
</draw:frame></text:p>
<text:p text:style-name="P4"><text:placeholder text:placeholder-type="text">&lt;/when&gt;</text:placeholder></text:p>
<text:p text:style-name="P4"><text:placeholder text:placeholder-type="text">&lt;when test=&quot;company&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P4"><text:placeholder text:placeholder-type="text">&lt;company.rec_name&gt;</text:placeholder></text:p>
<text:p text:style-name="P4"><text:placeholder text:placeholder-type="text">&lt;/when&gt;</text:placeholder></text:p>
<text:p text:style-name="P4"><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="P25"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;sale in records&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P31"/>
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text">&lt;replace text:p=&quot;set_lang(sale.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;sale.set_lang(sale.party.lang)&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P14"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;line in sale.report_address.splitlines()&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P14"><text:placeholder text:placeholder-type="text">&lt;line&gt;</text:placeholder></text:p>
<text:p text:style-name="P14"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
<text:p text:style-name="P14"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;sale.party.tax_identifier&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P14"><text:placeholder text:placeholder-type="text">&lt;sale.party.tax_identifier.type_string&gt;</text:placeholder>: <text:placeholder text:placeholder-type="text">&lt;sale.party.tax_identifier.code&gt;</text:placeholder></text:p>
<text:p text:style-name="P14"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
<text:p text:style-name="P12"><text:placeholder text:placeholder-type="text">&lt;choose test=&quot;&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P21"><text:placeholder text:placeholder-type="text">&lt;when test=&quot;sale.state == &apos;draft&apos;&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P30">Draft Sale Order</text:p>
<text:p text:style-name="P21"><text:placeholder text:placeholder-type="text">&lt;/when&gt;</text:placeholder></text:p>
<text:p text:style-name="P21"><text:placeholder text:placeholder-type="text">&lt;when test=&quot;sale.state == &apos;quotation&apos;&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P30"><text:soft-page-break/>Quotation No: <text:placeholder text:placeholder-type="text">&lt;sale.full_number&gt;</text:placeholder></text:p>
<text:p text:style-name="P21"><text:placeholder text:placeholder-type="text">&lt;/when&gt;</text:placeholder></text:p>
<text:p text:style-name="P21"><text:placeholder text:placeholder-type="text">&lt;otherwise test=&quot;&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P30">Sale Order No: <text:placeholder text:placeholder-type="text">&lt;sale.full_number&gt;</text:placeholder></text:p>
<text:p text:style-name="P21"><text:placeholder text:placeholder-type="text">&lt;/otherwise&gt;</text:placeholder></text:p>
<text:p text:style-name="P22"><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="P26">Description: <text:placeholder text:placeholder-type="text">&lt;sale.description or &apos;&apos;&gt;</text:placeholder></text:p>
<text:p text:style-name="P24">Reference: <text:placeholder text:placeholder-type="text">&lt;sale.reference or &apos;&apos;&gt;</text:placeholder></text:p>
<text:p text:style-name="P32">Date: <text:placeholder text:placeholder-type="text">&lt;format_date((sale.quotation_date if sale.state in [&apos;draft&apos;, &apos;quotation&apos;] and sale.quotation_date else sale.sale_date) or today, sale.party.lang)&gt;</text:placeholder></text:p>
<text:p text:style-name="P34"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;sale.state in [&apos;draft&apos;, &apos;quotation&apos;] and sale.quotation_expire&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P37">Expiration Date: <text:placeholder text:placeholder-type="text">&lt;format_date(sale.quotation_expire, sale.party.lang)&gt;</text:placeholder></text:p>
<text:p text:style-name="P34"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
<text:p text:style-name="P36">Delivery Address:</text:p>
<text:p text:style-name="P35"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;line in sale.delivery_full_address.splitlines()&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P35"><text:placeholder text:placeholder-type="text">&lt;line&gt;</text:placeholder></text:p>
<text:p text:style-name="P35"><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 sale.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>
<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="P27"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;line.product_name&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P27"><text:placeholder text:placeholder-type="text">&lt;line.product_name&gt;</text:placeholder></text:p>
<text:p text:style-name="P27"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
<text:p text:style-name="P27"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;line.description&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P27"><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="P29"><text:placeholder text:placeholder-type="text">&lt;description&gt;</text:placeholder></text:p>
<text:p text:style-name="P29"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
<text:p text:style-name="P29"><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="P19"><text:placeholder text:placeholder-type="text">&lt;format_number_symbol(line.quantity, sale.party.lang, line.unit, digits=line.unit.digits) if line.unit else format_number(line.quantity, sale.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="P20"><text:placeholder text:placeholder-type="text">&lt;format_currency(line.unit_price, sale.party.lang, sale.currency, digits=line.__class__.unit_price.digits[1])&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="P8"><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="P8"><text:placeholder text:placeholder-type="text">&lt;tax.description&gt;</text:placeholder></text:p>
<text:p text:style-name="P8"><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="P19"><text:placeholder text:placeholder-type="text">&lt;format_currency(line.amount, sale.party.lang, sale.currency)&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="P13"><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="P19"><text:placeholder text:placeholder-type="text">&lt;format_currency(line.amount, sale.party.lang, sale.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="P27"><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="P27"><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>
</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"/>
<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="P18">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="P19"><text:placeholder text:placeholder-type="text">&lt;format_currency(sale.untaxed_amount, sale.party.lang, sale.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="P18">Taxes:</text:p>
</table:table-cell>
<table:table-cell table:style-name="Table4.B2" office:value-type="string">
<text:p text:style-name="P19"><text:placeholder text:placeholder-type="text">&lt;format_currency(sale.tax_amount, sale.party.lang, sale.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="P18">Total:</text:p>
</table:table-cell>
<table:table-cell table:style-name="Table4.B2" office:value-type="string">
<text:p text:style-name="P19"><text:placeholder text:placeholder-type="text">&lt;format_currency(sale.total_amount, sale.party.lang, sale.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:soft-page-break/><text:placeholder text:placeholder-type="text">&lt;for each=&quot;comment in (sale.comment or &apos;&apos;).split(&apos;\n&apos;)&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="Text_20_body"><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>

2619
modules/sale/sale.py Normal file

File diff suppressed because it is too large Load Diff

565
modules/sale/sale.xml Normal file
View File

@@ -0,0 +1,565 @@
<?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_sale">
<field name="name">Sales</field>
</record>
<record model="res.group" id="group_sale_admin">
<field name="name">Sales Administrator</field>
<field name="parent" ref="group_sale"/>
</record>
<record model="res.user-res.group" id="user_admin_group_sale">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_sale"/>
</record>
<record model="res.user-res.group" id="user_admin_group_sale_admin">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_sale_admin"/>
</record>
<record model="ir.ui.icon" id="sale_icon">
<field name="name">tryton-sale</field>
<field name="path">icons/tryton-sale.svg</field>
</record>
<menuitem
name="Sales"
sequence="80"
id="menu_sale"
icon="tryton-sale"/>
<record model="ir.ui.menu-res.group" id="menu_sale_group_sale">
<field name="menu" ref="menu_sale"/>
<field name="group" ref="group_sale"/>
</record>
<record model="ir.action.wizard" id="wizard_shipment_handle_exception">
<field name="name">Handle Shipment Exception</field>
<field name="wiz_name">sale.handle.shipment.exception</field>
<field name="model">sale.sale</field>
</record>
<record model="ir.action.wizard" id="wizard_invoice_handle_exception">
<field name="name">Handle Invoice Exception</field>
<field name="wiz_name">sale.handle.invoice.exception</field>
<field name="model">sale.sale</field>
</record>
<record model="ir.ui.view" id="sale_view_form">
<field name="model">sale.sale</field>
<field name="type">form</field>
<field name="name">sale_form</field>
</record>
<record model="ir.ui.view" id="sale_view_tree">
<field name="model">sale.sale</field>
<field name="type">tree</field>
<field name="name">sale_tree</field>
</record>
<record model="ir.ui.view" id="handle_shipment_exception_ask_view_form">
<field name="model">sale.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">sale.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_shipment_form">
<field name="name">Shipments</field>
<field name="res_model">stock.shipment.out</field>
<field name="domain"
eval="[If(Eval('active_ids', []) == [Eval('active_id')], ('moves.sale', '=', Eval('active_id')), ('moves.sale', '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">sale.sale,-1</field>
<field name="action" ref="act_shipment_form"/>
</record>
<record model="ir.action.act_window" id="act_return_form">
<field name="name">Returns</field>
<field name="res_model">stock.shipment.out.return</field>
<field name="domain"
eval="[If(Eval('active_ids', []) == [Eval('active_id')], ('moves.sale', '=', Eval('active_id')), ('moves.sale', '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">sale.sale,-1</field>
<field name="action" ref="act_return_form"/>
</record>
<record model="ir.action.act_window" id="act_sale_form">
<field name="name">Sales</field>
<field name="res_model">sale.sale</field>
<field name="search_value"></field>
</record>
<record model="ir.action.act_window.view" id="act_sale_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="sale_view_tree"/>
<field name="act_window" ref="act_sale_form"/>
</record>
<record model="ir.action.act_window.view" id="act_sale_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="sale_view_form"/>
<field name="act_window" ref="act_sale_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_sale_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_sale_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_sale_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_sale_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_sale_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_sale_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_sale_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_sale_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_sale_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_sale_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_sale_form_domain_all">
<field name="name">All</field>
<field name="sequence" eval="9999"/>
<field name="domain"></field>
<field name="act_window" ref="act_sale_form"/>
</record>
<menuitem
parent="menu_sale"
action="act_sale_form"
sequence="10"
id="menu_sale_form"/>
<record model="ir.action.act_window" id="act_sale_relate">
<field name="name">Sales</field>
<field name="res_model">sale.sale</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_sale_relate_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="sale_view_tree"/>
<field name="act_window" ref="act_sale_relate"/>
</record>
<record model="ir.action.act_window.view" id="act_sale_relate_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="sale_view_form"/>
<field name="act_window" ref="act_sale_relate"/>
</record>
<record model="ir.action.act_window.domain" id="act_sale_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_sale_relate"/>
</record>
<record model="ir.action.act_window.domain" id="act_sale_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_sale_relate"/>
</record>
<record model="ir.action.act_window.domain" id="act_sale_relate_all">
<field name="name">All</field>
<field name="sequence" eval="9999"/>
<field name="domain"></field>
<field name="act_window" ref="act_sale_relate"/>
</record>
<record model="ir.action.keyword" id="act_sale_relate_keyword_party">
<field name="keyword">form_relate</field>
<field name="model">party.party,-1</field>
<field name="action" ref="act_sale_relate"/>
</record>
<record model="ir.action.act_window" id="act_sale_relate_simple">
<field name="name">Sales</field>
<field name="res_model">sale.sale</field>
<field
name="domain"
eval="[
If(Eval('active_model') == 'account.invoice',
('invoices', 'in', Eval('active_ids', [])), ()),
If(Eval('active_model') == 'stock.shipment.out',
('shipments', 'in', Eval('active_ids', [])), ()),
If(Eval('active_model') == 'stock.shipment.out.return',
('shipment_returns', 'in', Eval('active_ids', [])), ()),
]"
pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_sale_relate_simple_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="sale_view_tree"/>
<field name="act_window" ref="act_sale_relate_simple"/>
</record>
<record model="ir.action.act_window.view" id="act_sale_relate_simple_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="sale_view_form"/>
<field name="act_window" ref="act_sale_relate_simple"/>
</record>
<record model="ir.action.keyword" id="act_sale_relate_simple_keyword_invoice">
<field name="keyword">form_relate</field>
<field name="model">account.invoice,-1</field>
<field name="action" ref="act_sale_relate_simple"/>
</record>
<record model="ir.action.keyword" id="act_sale_relate_simple_keyword_shipment_out">
<field name="keyword">form_relate</field>
<field name="model">stock.shipment.out,-1</field>
<field name="action" ref="act_sale_relate_simple"/>
</record>
<record model="ir.action.keyword" id="act_sale_relate_simple_keyword_shipment_out_return">
<field name="keyword">form_relate</field>
<field name="model">stock.shipment.out.return,-1</field>
<field name="action" ref="act_sale_relate_simple"/>
</record>
<record model="ir.model.access" id="access_sale">
<field name="model">sale.sale</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_sale_sale">
<field name="model">sale.sale</field>
<field name="group" ref="group_sale"/>
<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.field.access" id="access_sale_sale_invoices_ignored">
<field name="model">sale.sale</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_sale_sale_invoices_ignored_sale_admin">
<field name="model">sale.sale</field>
<field name="field">invoices_ignored</field>
<field name="group" ref="group_sale_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
</record>
<record model="ir.rule.group" id="rule_group_sale_companies">
<field name="name">User in companies</field>
<field name="model">sale.sale</field>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_sale_companies">
<field name="domain"
eval="[('company', 'in', Eval('companies', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_sale_companies"/>
</record>
<record model="ir.model.button" id="sale_cancel_button">
<field name="model">sale.sale</field>
<field name="name">cancel</field>
<field name="string">Cancel</field>
</record>
<record model="ir.model.button" id="sale_draft_button">
<field name="model">sale.sale</field>
<field name="name">draft</field>
<field name="string">Draft</field>
</record>
<record model="ir.model.button" id="sale_quote_button">
<field name="model">sale.sale</field>
<field name="name">quote</field>
<field name="string">Quote</field>
</record>
<record model="ir.model.button" id="sale_confirm_button">
<field name="model">sale.sale</field>
<field name="name">confirm</field>
<field name="string">Confirm</field>
</record>
<record model="ir.model.button" id="sale_process_button">
<field name="model">sale.sale</field>
<field name="name">process</field>
<field name="string">Process</field>
</record>
<record model="ir.model.button-res.group" id="sale_process_button_group_sale_admin">
<field name="button" ref="sale_process_button"/>
<field name="group" ref="group_sale_admin"/>
</record>
<record model="ir.model.button" id="sale_manual_invoice_button">
<field name="model">sale.sale</field>
<field name="name">manual_invoice</field>
<field name="string">Create Invoice</field>
</record>
<record model="ir.model.button-res.group" id="sale_manual_invoice_button_group_sale">
<field name="button" ref="sale_manual_invoice_button"/>
<field name="group" ref="group_sale"/>
</record>
<record model="ir.model.button-res.group" id="sale_manual_invoice_button_group_account">
<field name="button" ref="sale_manual_invoice_button"/>
<field name="group" ref="account.group_account"/>
</record>
<record model="ir.model.button" id="sale_manual_shipment_button">
<field name="model">sale.sale</field>
<field name="name">manual_shipment</field>
<field name="string">Create Shipment</field>
</record>
<record model="ir.model.button-res.group" id="sale_manual_shipment_button_group_sale">
<field name="button" ref="sale_manual_shipment_button"/>
<field name="group" ref="group_sale"/>
</record>
<record model="ir.model.button-res.group" id="sale_manual_shipment_button_group_stock">
<field name="button" ref="sale_manual_shipment_button"/>
<field name="group" ref="stock.group_stock"/>
</record>
<record model="ir.model.button" id="sale_modify_header_button">
<field name="model">sale.sale</field>
<field name="name">modify_header</field>
<field name="string">Modify Header</field>
</record>
<record model="ir.model.button"
id="sale_handle_shipment_exception_button">
<field name="model">sale.sale</field>
<field name="name">handle_shipment_exception</field>
<field name="string">Handle Shipment Exception</field>
</record>
<record model="ir.model.button"
id="sale_handle_invoice_exception_button">
<field name="model">sale.sale</field>
<field name="name">handle_invoice_exception</field>
<field name="string">Handle Invoice Exception</field>
</record>
<record model="ir.sequence.type" id="sequence_type_sale">
<field name="name">Sale</field>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_sale_group_admin">
<field name="sequence_type" ref="sequence_type_sale"/>
<field name="group" ref="res.group_admin"/>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_sale_group_sale_admin">
<field name="sequence_type" ref="sequence_type_sale"/>
<field name="group" ref="group_sale_admin"/>
</record>
<record model="ir.sequence" id="sequence_sale">
<field name="name">Sale</field>
<field name="sequence_type" ref="sequence_type_sale"/>
</record>
<record model="ir.action.report" id="report_sale">
<field name="name">Sale</field>
<field name="model">sale.sale</field>
<field name="report_name">sale.sale</field>
<field name="report">sale/sale.fodt</field>
</record>
<record model="ir.action.keyword" id="report_sale_keyword">
<field name="keyword">form_print</field>
<field name="model">sale.sale,-1</field>
<field name="action" ref="report_sale"/>
</record>
<record model="ir.ui.view" id="sale_line_view_form">
<field name="model">sale.line</field>
<field name="type">form</field>
<field name="name">sale_line_form</field>
</record>
<record model="ir.ui.view" id="sale_line_view_tree">
<field name="model">sale.line</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">sale_line_tree</field>
</record>
<record model="ir.ui.view" id="sale_line_view_tree_sequence">
<field name="model">sale.line</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">sale_line_tree_sequence</field>
</record>
<record model="ir.model.field.access" id="access_sale_line_moves_ignored">
<field name="model">sale.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_sale_line_moves_ignored_sale_admin">
<field name="model">sale.line</field>
<field name="field">moves_ignored</field>
<field name="group" ref="group_sale_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
</record>
<record model="ir.action.act_window" id="act_sale_line_relate">
<field name="name">Sale Lines</field>
<field name="res_model">sale.line</field>
<field
name="domain"
eval="[('type', '=', 'line'),
If(Eval('active_model') == 'sale.sale',
('sale', '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',
('customer', 'in', Eval('active_ids', [])), ()),
]"
pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_sale_line_relate_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="sale_line_view_tree"/>
<field name="act_window" ref="act_sale_line_relate"/>
</record>
<record model="ir.action.act_window.view" id="act_sale_line_relate_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="sale_line_view_form"/>
<field name="act_window" ref="act_sale_line_relate"/>
</record>
<record model="ir.action.act_window.domain" id="act_sale_line_relate_pending">
<field name="name">Pending</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('sale_state', 'not in', ['done', 'cancelled'])]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_sale_line_relate"/>
</record>
<record model="ir.action.act_window.domain" id="act_sale_line_relate_done">
<field name="name">Done</field>
<field name="sequence" eval="20"/>
<field name="domain" eval="[('sale_state', '=', 'done')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_sale_line_relate"/>
</record>
<record model="ir.action.act_window.domain" id="act_sale_line_relate_all">
<field name="name">All</field>
<field name="sequence" eval="9999"/>
<field name="domain"></field>
<field name="act_window" ref="act_sale_line_relate"/>
</record>
<record model="ir.action.keyword" id="act_sale_line_relate_keyword_sale">
<field name="keyword">form_relate</field>
<field name="model">sale.sale,-1</field>
<field name="action" ref="act_sale_line_relate"/>
</record>
<record model="ir.action.keyword" id="act_sale_line_relate_keyword_product">
<field name="keyword">form_relate</field>
<field name="model">product.product,-1</field>
<field name="action" ref="act_sale_line_relate"/>
</record>
<record model="ir.action.keyword" id="act_sale_line_relate_keyword_product_template">
<field name="keyword">form_relate</field>
<field name="model">product.template,-1</field>
<field name="action" ref="act_sale_line_relate"/>
</record>
<record model="ir.action.keyword" id="act_sale_line_relate_keyword_party">
<field name="keyword">form_relate</field>
<field name="model">party.party,-1</field>
<field name="action" ref="act_sale_line_relate"/>
</record>
<record model="ir.ui.view" id="return_sale_start_view_form">
<field name="model">sale.return_sale.start</field>
<field name="type">form</field>
<field name="name">return_sale_start_form</field>
</record>
<record model="ir.action.wizard" id="wizard_return_sale">
<field name="name">Return Sale</field>
<field name="wiz_name">sale.return_sale</field>
<field name="model">sale.sale</field>
</record>
<record model="ir.action.keyword" id="act_wizard_return_sale_keyword">
<field name="keyword">form_action</field>
<field name="model">sale.sale,-1</field>
<field name="action" ref="wizard_return_sale"/>
</record>
<record model="ir.action.wizard" id="wizard_modify_header">
<field name="name">Modify Header</field>
<field name="wiz_name">sale.modify_header</field>
<field name="model">sale.sale</field>
</record>
<record model="ir.ui.view" id="modify_header_form">
<field name="model">sale.sale</field>
<field name="inherit" ref="sale.sale_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.action.wizard" id="wizard_sale_open_product">
<field name="name">Products</field>
<field name="wiz_name">sale.open_product</field>
<field name="model">sale.sale</field>
</record>
<record model="ir.action.keyword" id="wizard_sale_open_product_keyword1">
<field name="keyword">form_relate</field>
<field name="model">sale.sale,-1</field>
<field name="action" ref="wizard_sale_open_product"/>
</record>
</data>
<data noupdate="1">
<record model="ir.cron" id="cron_cancel_expired_quotation">
<field name="method">sale.sale|cancel_expired_quotation</field>
<field name="interval_number" eval="1"/>
<field name="interval_type">days</field>
</record>
</data>
</tryton>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

236
modules/sale/stock.py Normal file
View File

@@ -0,0 +1,236 @@
# 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 decimal import Decimal
from functools import wraps
from trytond.i18n import gettext
from trytond.model import ModelView, Workflow, fields
from trytond.model.exceptions import AccessError
from trytond.modules.product import round_price
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction, without_check_access
def process_sale(moves_field):
def _process_sale(func):
@wraps(func)
def wrapper(cls, shipments):
pool = Pool()
Sale = pool.get('sale.sale')
transaction = Transaction()
context = transaction.context
with without_check_access():
sales = set(m.sale for s in cls.browse(shipments)
for m in getattr(s, moves_field) if m.sale)
result = func(cls, shipments)
if sales:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Sale.__queue__.process(sales)
return result
return wrapper
return _process_sale
class ShipmentOut(metaclass=PoolMeta):
__name__ = 'stock.shipment.out'
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, shipments):
SaleLine = Pool().get('sale.line')
for shipment in shipments:
for move in shipment.outgoing_moves:
if (move.state == 'cancelled'
and isinstance(move.origin, SaleLine)):
raise AccessError(
gettext('sale.msg_sale_move_reset_draft',
move=move.rec_name))
return super().draft(shipments)
@classmethod
@ModelView.button
@Workflow.transition('shipped')
@process_sale('outgoing_moves')
def ship(cls, shipments):
super().ship(shipments)
@classmethod
@ModelView.button
@Workflow.transition('done')
@process_sale('outgoing_moves')
def do(cls, shipments):
super().do(shipments)
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
@process_sale('outgoing_moves')
def cancel(cls, shipments):
super().cancel(shipments)
class ShipmentOutReturn(metaclass=PoolMeta):
__name__ = 'stock.shipment.out.return'
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, shipments):
SaleLine = Pool().get('sale.line')
for shipment in shipments:
for move in shipment.incoming_moves:
if (move.state == 'cancelled'
and isinstance(move.origin, SaleLine)):
raise AccessError(
gettext('sale.msg_sale_move_reset_draft',
move=move.rec_name))
return super().draft(shipments)
@classmethod
@ModelView.button
@Workflow.transition('received')
@process_sale('incoming_moves')
def receive(cls, shipments):
pool = Pool()
SaleLine = pool.get('sale.line')
for shipment in shipments:
for move in shipment.incoming_moves:
if isinstance(move.origin, SaleLine):
move.origin.check_move_quantity()
super().receive(shipments)
def process_sale_move(func):
@wraps(func)
def wrapper(cls, moves):
pool = Pool()
Sale = pool.get('sale.sale')
transaction = Transaction()
context = transaction.context
with without_check_access():
sales = set(m.sale for m in cls.browse(moves) if m.sale)
result = func(cls, moves)
if sales:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Sale.__queue__.process(sales)
return result
return wrapper
class Move(metaclass=PoolMeta):
__name__ = 'stock.move'
sale = fields.Function(
fields.Many2One('sale.sale', "Sale"),
'get_sale', searcher='search_sale')
sale_exception_state = fields.Function(fields.Selection([
('', ''),
('ignored', 'Ignored'),
('recreated', 'Recreated'),
], 'Exception State'), 'get_sale_exception_state')
@classmethod
def __setup__(cls):
super().__setup__()
if not cls.origin.domain:
cls.origin.domain = {}
cls.origin.domain['sale.line'] = [
('type', '=', 'line'),
]
@classmethod
def _get_origin(cls):
models = super()._get_origin()
models.append('sale.line')
return models
@classmethod
def check_origin_types(cls):
types = super().check_origin_types()
types.add('customer')
return types
def get_sale(self, name):
SaleLine = Pool().get('sale.line')
if isinstance(self.origin, SaleLine):
return self.origin.sale.id
@classmethod
def search_sale(cls, name, clause):
return [('origin.' + clause[0],) + tuple(clause[1:3])
+ ('sale.line',) + tuple(clause[3:])]
def get_sale_exception_state(self, name):
SaleLine = Pool().get('sale.line')
if not isinstance(self.origin, SaleLine):
return ''
if self in self.origin.moves_recreated:
return 'recreated'
if self in self.origin.moves_ignored:
return 'ignored'
return ''
@fields.depends('origin')
def on_change_with_product_uom_category(self, name=None):
pool = Pool()
SaleLine = pool.get('sale.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 ship and the quantity to invoice.
# Use getattr as reference field can have negative id
if (isinstance(self.origin, SaleLine)
and getattr(self.origin, 'unit', None)):
category = self.origin.unit.category
return category
def get_cost_price(self, product_cost_price=None):
pool = Pool()
SaleLine = pool.get('sale.line')
Sale = pool.get('sale.sale')
# For return sale's move use the cost price of the original sale
if (isinstance(self.origin, SaleLine)
and self.origin.quantity < 0
and self.from_location.type != 'storage'
and self.to_location.type == 'storage'
and isinstance(self.origin.sale.origin, Sale)):
sale = self.origin.sale.origin
cost = Decimal(0)
qty = Decimal(0)
for move in sale.moves:
if (move.state == 'done'
and move.from_location.type == 'storage'
and move.to_location.type == 'customer'
and move.product == self.product):
move_quantity = Decimal(str(move.internal_quantity))
cost_price = move.get_cost_price(
product_cost_price=move.cost_price)
qty += move_quantity
cost += cost_price * move_quantity
if qty:
product_cost_price = round_price(cost / qty)
return super().get_cost_price(product_cost_price=product_cost_price)
@property
def origin_name(self):
pool = Pool()
SaleLine = pool.get('sale.line')
name = super().origin_name
if isinstance(self.origin, SaleLine) and self.origin.id >= 0:
name = self.origin.sale.rec_name
return name
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
@process_sale_move
def cancel(cls, moves):
super().cancel(moves)
@classmethod
@process_sale_move
def on_delete(cls, moves):
return super().on_delete(moves)

74
modules/sale/stock.xml Normal file
View File

@@ -0,0 +1,74 @@
<?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_sale">
<field name="model">stock.move</field>
<field name="group" ref="group_sale"/>
<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_sale_move_relate">
<field name="name">Stock Moves</field>
<field name="res_model">stock.move</field>
<field
name="domain"
eval="[
If(Eval('active_model') == 'sale.sale',
('sale', 'in', Eval('active_ids', [])), ()),
If(Eval('active_model') == 'sale.line',
('origin.id', 'in', Eval('active_ids', []), 'sale.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_sale_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_sale_move_relate"/>
</record>
<record model="ir.action.keyword" id="act_move_form_keyword_sale">
<field name="keyword">form_relate</field>
<field name="model">sale.sale,-1</field>
<field name="action" ref="act_sale_move_relate"/>
</record>
<record model="ir.action.keyword" id="act_move_form_keyword_sale_line">
<field name="keyword">form_relate</field>
<field name="model">sale.line,-1</field>
<field name="action" ref="act_sale_move_relate"/>
</record>
<record model="ir.model.access" id="access_shipment_out_group_sale">
<field name="model">stock.shipment.out</field>
<field name="group" ref="group_sale"/>
<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_shipment_out_return_group_sale">
<field name="model">stock.shipment.out.return</field>
<field name="group" ref="group_sale"/>
<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,703 @@
=============
Sale Scenario
=============
Imports::
>>> from decimal import Decimal
>>> from proteus import Model, Report, Wizard
>>> 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
Activate modules::
>>> config = activate_modules('sale', 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())
>>> 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.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.customer_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.salable = True
>>> template.list_price = Decimal('10')
>>> template.account_category = account_category_tax
>>> template.save()
>>> product, = template.products
>>> template = ProductTemplate()
>>> template.name = 'service'
>>> template.default_uom = unit
>>> template.type = 'service'
>>> template.salable = True
>>> template.list_price = Decimal('30')
>>> 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'
Sale 5 products::
>>> Sale = Model.get('sale.sale')
>>> SaleLine = Model.get('sale.line')
>>> sale = Sale()
>>> sale.party = customer
>>> sale.payment_term = payment_term
>>> sale.invoice_method = 'order'
>>> sale_line = SaleLine()
>>> sale.lines.append(sale_line)
>>> sale_line.product = product
>>> sale_line.quantity = 2.0
>>> sale_line = SaleLine()
>>> sale.lines.append(sale_line)
>>> sale_line.type = 'comment'
>>> sale_line.description = 'Comment'
>>> sale_line = SaleLine()
>>> sale.lines.append(sale_line)
>>> sale_line.product = product
>>> sale_line.quantity = 3.0
>>> sale.click('quote')
>>> sale.untaxed_amount, sale.tax_amount, sale.total_amount
(Decimal('50.00'), Decimal('5.00'), Decimal('55.00'))
>>> assertEqual(sale.quoted_by, employee)
>>> sale.click('confirm')
>>> sale.untaxed_amount, sale.tax_amount, sale.total_amount
(Decimal('50.00'), Decimal('5.00'), Decimal('55.00'))
>>> assertEqual(sale.confirmed_by, employee)
>>> sale.state
'processing'
>>> sale.shipment_state
'waiting'
>>> sale.invoice_state
'pending'
>>> len(sale.shipments), len(sale.shipment_returns), len(sale.invoices)
(1, 0, 1)
>>> invoice, = sale.invoices
>>> assertEqual(invoice.origins, sale.rec_name)
>>> shipment, = sale.shipments
>>> assertEqual(shipment.origins, sale.rec_name)
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(shipment.outgoing_moves,
... key=lambda m: m.quantity or 0)
>>> 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 sale.lines:
... assertEqual(line.quantity, line.actual_quantity)
Post invoice and check no new invoices::
>>> for invoice in sale.invoices:
... invoice.click('post')
>>> sale.reload()
>>> len(sale.shipments), len(sale.shipment_returns), len(sale.invoices)
(1, 0, 1)
>>> sale.invoice_state
'awaiting payment'
Testing the report::
>>> sale_report = Report('sale.sale')
>>> ext, _, _, name = sale_report.execute([sale], {})
>>> ext
'odt'
>>> name
'Sale-1'
Sale 5 products with an invoice method 'on shipment'::
>>> Sale = Model.get('sale.sale')
>>> SaleLine = Model.get('sale.line')
>>> sale = Sale()
>>> sale.party = customer
>>> sale.payment_term = payment_term
>>> sale.invoice_method = 'shipment'
>>> sale_line = SaleLine()
>>> sale.lines.append(sale_line)
>>> sale_line.product = product
>>> sale_line.quantity = 2.0
>>> sale_line = SaleLine()
>>> sale.lines.append(sale_line)
>>> sale_line.type = 'comment'
>>> sale_line.description = 'Comment'
>>> sale_line = SaleLine()
>>> sale.lines.append(sale_line)
>>> sale_line.product = product
>>> sale_line.quantity = 3.0
>>> sale.click('quote')
>>> sale.click('confirm')
>>> sale.state
'processing'
>>> sale.shipment_state
'waiting'
>>> sale.invoice_state
'none'
>>> sale.reload()
>>> len(sale.shipments), len(sale.shipment_returns), len(sale.invoices)
(1, 0, 0)
Not yet linked to invoice lines::
>>> shipment, = sale.shipments
>>> stock_move1, stock_move2 = sorted(shipment.outgoing_moves,
... key=lambda m: m.quantity or 0)
>>> len(stock_move1.invoice_lines)
0
>>> len(stock_move2.invoice_lines)
0
Validate Shipments::
>>> shipment.click('assign_try')
>>> shipment.click('pick')
>>> shipment.click('pack')
>>> shipment.click('do')
Open customer invoice::
>>> sale.reload()
>>> sale.invoice_state
'pending'
>>> invoice, = sale.invoices
>>> invoice.type
'out'
>>> 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.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::
>>> sale.reload()
>>> len(sale.invoices)
2
>>> sum(l.quantity for i in sale.invoices for l in i.lines)
5.0
Sale 5 products with shipment method 'on invoice'::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.payment_term = payment_term
>>> sale.shipment_method = 'invoice'
>>> sale_line = sale.lines.new()
>>> sale_line.product = product
>>> sale_line.quantity = 5.0
>>> sale.click('quote')
>>> sale.click('confirm')
>>> sale.state
'processing'
>>> sale.shipment_state
'none'
>>> sale.invoice_state
'pending'
>>> len(sale.shipments), len(sale.shipment_returns), len(sale.invoices)
(0, 0, 1)
Not yet linked to stock moves::
>>> invoice, = sale.invoices
>>> invoice_line, = invoice.lines
>>> len(invoice_line.stock_moves)
0
Post and Pay Invoice for 4 products::
>>> invoice_line, = invoice.lines
>>> invoice_line.quantity
5.0
>>> invoice_line.quantity = 4.0
>>> invoice.click('post')
>>> pay = invoice.click('pay')
>>> pay.form.payment_method = payment_method
>>> pay.execute('choice')
>>> invoice.reload()
>>> invoice.state
'paid'
Invoice lines linked to 1 move::
>>> invoice_line, = invoice.lines
>>> len(invoice_line.stock_moves)
1
Stock moves must be linked to invoice line::
>>> sale.reload()
>>> shipment, = sale.shipments
>>> shipment.reload()
>>> stock_move, = shipment.outgoing_moves
>>> stock_move.quantity
4.0
>>> assertEqual(stock_move.invoice_lines, [invoice_line])
Ship 3 products::
>>> stock_inventory_move, = shipment.inventory_moves
>>> stock_inventory_move.quantity
4.0
>>> stock_inventory_move.quantity = 3.0
>>> shipment.click('assign_try')
>>> shipment.click('pick')
>>> shipment.click('pack')
>>> shipment.click('do')
>>> shipment.state
'done'
New shipments created::
>>> sale.reload()
>>> len(sale.shipments)
2
Invoice lines linked to new moves::
>>> invoice.reload()
>>> invoice_line, = invoice.lines
>>> len(invoice_line.stock_moves)
2
Create a Return::
>>> return_ = Sale()
>>> return_.party = customer
>>> return_.payment_term = payment_term
>>> return_.invoice_method = 'shipment'
>>> return_line = SaleLine()
>>> return_.lines.append(return_line)
>>> return_line.product = product
>>> return_line.quantity = -4.
>>> return_line = SaleLine()
>>> 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)
Receive Return Shipment for 3 products::
>>> ship_return, = return_.shipment_returns
>>> move_return, = ship_return.incoming_moves
>>> move_return.product.rec_name
'product'
>>> move_return.quantity
4.0
>>> move_return.quantity = 3
>>> ship_return.click('receive')
Check Return::
>>> return_.reload()
>>> return_.shipment_state
'partially shipped'
>>> return_.invoice_state
'pending'
>>> (len(return_.shipments), len(return_.shipment_returns),
... len(return_.invoices))
(0, 2, 1)
Open customer credit note::
>>> credit_note, = return_.invoices
>>> credit_note.type
'out'
>>> len(credit_note.lines)
1
>>> sum(l.quantity for l in credit_note.lines)
-3.0
>>> credit_note.click('post')
Receive Remaining Return Shipment::
>>> return_.reload()
>>> _, ship_return = return_.shipment_returns
>>> move_return, = ship_return.incoming_moves
>>> move_return.product.rec_name
'product'
>>> move_return.quantity
1.0
>>> ship_return.click('receive')
Check Return::
>>> return_.reload()
>>> return_.shipment_state
'sent'
>>> return_.invoice_state
'awaiting payment'
>>> (len(return_.shipments), len(return_.shipment_returns),
... len(return_.invoices))
(0, 2, 2)
Mixing return and sale::
>>> mix = Sale()
>>> mix.party = customer
>>> mix.payment_term = payment_term
>>> mix.invoice_method = 'order'
>>> mixline = SaleLine()
>>> mix.lines.append(mixline)
>>> mixline.product = product
>>> mixline.quantity = 7.
>>> mixline_comment = SaleLine()
>>> mix.lines.append(mixline_comment)
>>> mixline_comment.type = 'comment'
>>> mixline_comment.description = 'Comment'
>>> mixline2 = SaleLine()
>>> 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.shipments), len(mix.shipment_returns), len(mix.invoices)
(1, 1, 1)
Checking Shipments::
>>> mix_return, = mix.shipment_returns
>>> mix_shipment, = mix.shipments
>>> mix_return.click('receive')
>>> move_return, = mix_return.incoming_moves
>>> move_return.product.rec_name
'product'
>>> move_return.quantity
2.0
>>> mix_shipment.click('assign_try')
>>> mix_shipment.click('pick')
>>> mix_shipment.click('pack')
>>> mix_shipment.click('do')
>>> move_shipment, = mix_shipment.outgoing_moves
>>> move_shipment.product.rec_name
'product'
>>> move_shipment.quantity
7.0
Checking the invoice::
>>> mix.reload()
>>> mix_invoice, = mix.invoices
>>> mix_invoice.type
'out'
>>> len(mix_invoice.lines)
2
>>> sorted(l.quantity for l in mix_invoice.lines)
[-2.0, 7.0]
>>> mix_invoice.click('post')
Mixing stuff with an invoice method 'on shipment'::
>>> mix = Sale()
>>> mix.party = customer
>>> mix.payment_term = payment_term
>>> mix.invoice_method = 'shipment'
>>> mixline = SaleLine()
>>> mix.lines.append(mixline)
>>> mixline.product = product
>>> mixline.quantity = 6.
>>> mixline_comment = SaleLine()
>>> mix.lines.append(mixline_comment)
>>> mixline_comment.type = 'comment'
>>> mixline_comment.description = 'Comment'
>>> mixline2 = SaleLine()
>>> 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.shipments), len(mix.shipment_returns), len(mix.invoices)
(1, 1, 0)
Checking Shipments::
>>> mix_return, = mix.shipment_returns
>>> mix_shipment, = mix.shipments
>>> mix_return.click('receive')
>>> move_return, = mix_return.incoming_moves
>>> move_return.product.rec_name
'product'
>>> move_return.quantity
3.0
>>> mix_shipment.click('assign_try')
>>> mix_shipment.click('pick')
>>> mix_shipment.click('pack')
>>> move_shipment, = mix_shipment.outgoing_moves
>>> move_shipment.product.rec_name
'product'
>>> move_shipment.quantity
6.0
Sale services::
>>> service_sale = Sale()
>>> service_sale.party = customer
>>> service_sale.payment_term = payment_term
>>> sale_line = service_sale.lines.new()
>>> sale_line.product = service
>>> sale_line.quantity = 1
>>> service_sale.save()
>>> service_sale.click('quote')
>>> service_sale.click('confirm')
>>> service_sale.state
'processing'
>>> service_sale.shipment_state
'none'
>>> service_sale.invoice_state
'pending'
>>> service_invoice, = service_sale.invoices
Pay the service invoice::
>>> service_invoice.click('post')
>>> pay = service_invoice.click('pay')
>>> pay.form.payment_method = payment_method
>>> pay.execute('choice')
>>> service_invoice.reload()
>>> service_invoice.state
'paid'
Check service sale states::
>>> service_sale.reload()
>>> service_sale.invoice_state
'paid'
>>> service_sale.shipment_state
'none'
>>> service_sale.state
'done'
Return sales using the wizard::
>>> sale_to_return = Sale()
>>> sale_to_return.party = customer
>>> sale_to_return.payment_term = payment_term
>>> sale_line = sale_to_return.lines.new()
>>> sale_line.product = service
>>> sale_line.quantity = 1
>>> sale_line = sale_to_return.lines.new()
>>> sale_line.type = 'comment'
>>> sale_line.description = 'Test comment'
>>> sale_to_return.click('quote')
>>> sale_to_return.click('confirm')
>>> sale_to_return.state
'processing'
>>> return_sale = Wizard('sale.return_sale', [sale_to_return])
>>> return_sale.execute('return_')
>>> returned_sale, = Sale.find([
... ('state', '=', 'draft'),
... ])
>>> assertEqual(returned_sale.origin, sale_to_return)
>>> sorted([x.quantity or 0 for x in returned_sale.lines])
[-1.0, 0]
Create a sale to be invoiced on shipment partialy and check correctly linked
to invoices::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.payment_term = payment_term
>>> sale.invoice_method = 'shipment'
>>> line = sale.lines.new()
>>> line.product = product
>>> line.quantity = 10.0
>>> sale.click('quote')
>>> sale.click('confirm')
>>> shipment, = sale.shipments
>>> for move in shipment.inventory_moves:
... move.quantity = 5.0
>>> shipment.click('assign_try')
>>> shipment.click('pick')
>>> shipment.click('pack')
>>> shipment.click('do')
>>> sale.reload()
>>> invoice, = sale.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 sale to be sent on invoice partially and check correctly linked to
invoices::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.payment_term = payment_term
>>> sale.shipment_method = 'invoice'
>>> line = sale.lines.new()
>>> line.product = product
>>> line.quantity = 10.0
>>> sale.click('quote')
>>> sale.click('confirm')
>>> invoice, = sale.invoices
>>> invoice_line, = invoice.lines
>>> assertEqual(invoice_line.stock_moves, [])
>>> invoice_line.quantity = 5.0
>>> invoice.click('post')
>>> pay = invoice.click('pay')
>>> pay.form.payment_method = payment_method
>>> pay.execute('choice')
>>> invoice.reload()
>>> invoice.state
'paid'
>>> sale.reload()
>>> sale.invoice_state
'partially paid'
>>> invoice_line.reload()
>>> stock_move, = invoice_line.stock_moves
>>> stock_move.quantity
5.0
>>> stock_move.state
'draft'
Deleting a line from a invoice should recreate it::
>>> sale = Sale()
>>> sale.party = customer
>>> line = sale.lines.new()
>>> line.product = product
>>> line.quantity = 10.0
>>> sale.click('quote')
>>> sale.click('confirm')
>>> invoice, = sale.invoices
>>> invoice_line, = invoice.lines
>>> invoice.lines.remove(invoice_line)
>>> invoice.click('post')
>>> sale.reload()
>>> new_invoice, = sale.invoices
>>> new_invoice.number
>>> len(new_invoice.lines)
1

View File

@@ -0,0 +1,31 @@
=============================
Sale Default Methods Scenario
=============================
Imports::
>>> from proteus import Model
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules
Activate modules::
>>> config = activate_modules('sale', create_company)
Create a party and set their default methods::
>>> Party = Model.get('party.party')
>>> customer = Party(name='Customer')
>>> customer.sale_shipment_method = 'invoice'
>>> customer.sale_invoice_method = 'shipment'
>>> customer.save()
Create a sale to to check default methods::
>>> Sale = Model.get('sale.sale')
>>> sale = Sale()
>>> sale.party = customer
>>> sale.shipment_method
'invoice'
>>> sale.invoice_method
'shipment'

View File

@@ -0,0 +1,59 @@
=======================
Sale 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('sale', create_company, create_chart)
>>> Party = Model.get('party.party')
>>> Sale = Model.get('sale.sale')
>>> Tax = Model.get('account.tax')
Get accounts::
>>> accounts = get_accounts()
Create tax::
>>> tax = create_tax(Decimal('.10'))
>>> tax.save()
>>> accounts['revenue'].taxes.append(Tax(tax.id))
>>> accounts['revenue'].save()
Create parties::
>>> customer = Party(name="Customer")
>>> customer.save()
Create a sale without product::
>>> sale = Sale(party=customer)
>>> line = sale.lines.new()
>>> assertEqual(line.taxes, [tax])
>>> line.quantity = 1
>>> line.unit_price = Decimal('100.0000')
>>> sale.click('quote')
>>> sale.total_amount
Decimal('110.00')
>>> sale.click('confirm')
>>> sale.state
'processing'
Check invoice::
>>> invoice, = sale.invoices
>>> invoice.total_amount
Decimal('110.00')
>>> line, = invoice.lines
>>> assertEqual(line.account, accounts['revenue'])

View File

@@ -0,0 +1,48 @@
===================
Sale 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('sale', create_company, create_chart)
Create parties::
>>> Party = Model.get('party.party')
>>> customer = Party(name='Customer')
>>> customer.save()
Create empty sale::
>>> Sale = Model.get('sale.sale')
>>> sale = Sale()
>>> sale.party = customer
>>> sale.click('quote')
>>> sale.state
'quotation'
>>> sale.untaxed_amount
Decimal('0')
>>> sale.tax_amount
Decimal('0')
>>> sale.total_amount
Decimal('0')
>>> sale.click('confirm')
>>> sale.state
'done'
>>> sale.shipment_state
'none'
>>> len(sale.shipments)
0
>>> len(sale.shipment_returns)
0
>>> sale.invoice_state
'none'
>>> len(sale.invoices)
0

View File

@@ -0,0 +1,103 @@
============================
Sale 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('sale', create_company, create_chart)
>>> Party = Model.get('party.party')
>>> ProductCategory = Model.get('product.category')
>>> ProductTemplate = Model.get('product.template')
>>> ProductUom = Model.get('product.uom')
>>> Sale = Model.get('sale.sale')
Get accounts::
>>> accounts = get_accounts()
Create party::
>>> customer = Party(name="Customer")
>>> customer.save()
Create product::
>>> account_category = ProductCategory(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_revenue = accounts['revenue']
>>> account_category.save()
>>> template = ProductTemplate(name="Product")
>>> template.default_uom, = ProductUom.find([('name', '=', "Unit")])
>>> template.type = 'goods'
>>> template.salable = True
>>> template.list_price = Decimal('10.0000')
>>> template.account_category = account_category
>>> template.save()
>>> product, = template.products
Sale product::
>>> sale = Sale(party=customer)
>>> line = sale.lines.new()
>>> line.product = product
>>> line.quantity = 1
>>> sale.click('quote')
>>> sale.click('confirm')
>>> sale.state
'processing'
>>> sale.shipment_state
'waiting'
>>> sale.invoice_state
'pending'
Cancel shipment and invoice::
>>> shipment, = sale.shipments
>>> shipment.click('cancel')
>>> shipment.state
'cancelled'
>>> invoice, = sale.invoices
>>> invoice.click('cancel')
>>> invoice.state
'cancelled'
>>> sale.reload()
>>> sale.state
'processing'
>>> sale.shipment_state
'exception'
>>> sale.invoice_state
'exception'
Ignore exceptions::
>>> invoice_handle_exception = sale.click('handle_invoice_exception')
>>> invoice_handle_exception.form.ignore_invoices.extend(
... invoice_handle_exception.form.ignore_invoices.find())
>>> invoice_handle_exception.execute('handle')
>>> sale.invoice_state
'none'
>>> shipment_handle_exception = sale.click('handle_shipment_exception')
>>> shipment_handle_exception.form.ignore_moves.extend(
... shipment_handle_exception.form.ignore_moves.find())
>>> shipment_handle_exception.execute('handle')
>>> sale.shipment_state
'none'
>>> sale.state
'done'

View File

@@ -0,0 +1,91 @@
=======================================
Sale Line Cancelled On 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('sale', create_company, create_chart)
>>> Party = Model.get('party.party')
>>> ProductCategory = Model.get('product.category')
>>> ProductTemplate = Model.get('product.template')
>>> ProductUom = Model.get('product.uom')
>>> Sale = Model.get('sale.sale')
Get accounts::
>>> accounts = get_accounts()
Create party::
>>> customer = Party(name="Customer")
>>> customer.save()
Create product::
>>> account_category = ProductCategory(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_revenue = accounts['revenue']
>>> account_category.save()
>>> template = ProductTemplate(name="Product")
>>> template.default_uom, = ProductUom.find([('name', '=', "Unit")])
>>> template.type = 'goods'
>>> template.salable = True
>>> template.list_price = Decimal('10.0000')
>>> template.account_category = account_category
>>> template.save()
>>> product, = template.products
Sale product::
>>> sale = Sale(party=customer)
>>> sale.shipment_method = 'invoice'
>>> line = sale.lines.new()
>>> line.product = product
>>> line.quantity = 1
>>> sale.click('quote')
>>> sale.click('confirm')
>>> sale.state
'processing'
>>> sale.shipment_state
'none'
>>> sale.invoice_state
'pending'
Cancel invoice::
>>> invoice, = sale.invoices
>>> invoice.click('cancel')
>>> invoice.state
'cancelled'
>>> sale.reload()
>>> sale.state
'processing'
>>> sale.shipment_state
'none'
>>> sale.invoice_state
'exception'
Ignore exception::
>>> invoice_handle_exception = sale.click('handle_invoice_exception')
>>> invoice_handle_exception.form.ignore_invoices.extend(
... invoice_handle_exception.form.ignore_invoices.find())
>>> invoice_handle_exception.execute('handle')
>>> sale.shipment_state
'none'
>>> sale.state
'done'

View File

@@ -0,0 +1,91 @@
========================================
Sale 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('sale', create_company, create_chart)
>>> Party = Model.get('party.party')
>>> ProductCategory = Model.get('product.category')
>>> ProductTemplate = Model.get('product.template')
>>> ProductUom = Model.get('product.uom')
>>> Sale = Model.get('sale.sale')
Get accounts::
>>> accounts = get_accounts()
Create party::
>>> customer = Party(name="Customer")
>>> customer.save()
Create product::
>>> account_category = ProductCategory(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_revenue = accounts['revenue']
>>> account_category.save()
>>> template = ProductTemplate(name="Product")
>>> template.default_uom, = ProductUom.find([('name', '=', "Unit")])
>>> template.type = 'goods'
>>> template.salable = True
>>> template.list_price = Decimal('10.0000')
>>> template.account_category = account_category
>>> template.save()
>>> product, = template.products
Sale product::
>>> sale = Sale(party=customer)
>>> sale.invoice_method = 'shipment'
>>> line = sale.lines.new()
>>> line.product = product
>>> line.quantity = 1
>>> sale.click('quote')
>>> sale.click('confirm')
>>> sale.state
'processing'
>>> sale.shipment_state
'waiting'
>>> sale.invoice_state
'none'
Cancel shipment::
>>> shipment, = sale.shipments
>>> shipment.click('cancel')
>>> shipment.state
'cancelled'
>>> sale.reload()
>>> sale.state
'processing'
>>> sale.shipment_state
'exception'
>>> sale.invoice_state
'none'
Ignore exception::
>>> shipment_handle_exception = sale.click('handle_shipment_exception')
>>> shipment_handle_exception.form.ignore_moves.extend(
... shipment_handle_exception.form.ignore_moves.find())
>>> shipment_handle_exception.execute('handle')
>>> sale.shipment_state
'none'
>>> sale.state
'done'

View File

@@ -0,0 +1,90 @@
============================
Sale 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('sale', create_company, create_chart)
>>> Party = Model.get('party.party')
>>> ProductCategory = Model.get('product.category')
>>> ProductTemplate = Model.get('product.template')
>>> ProductUom = Model.get('product.uom')
>>> Sale = Model.get('sale.sale')
Get accounts::
>>> accounts = get_accounts()
Create party::
>>> customer = Party(name="Customer")
>>> customer.save()
Create account category::
>>> account_category = ProductCategory(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_revenue = accounts['revenue']
>>> account_category.save()
Create product::
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> template = ProductTemplate()
>>> template.name = 'product'
>>> template.default_uom = unit
>>> template.type = 'service'
>>> template.salable = True
>>> template.account_category = account_category
>>> template.list_price = Decimal('10')
>>> template.save()
>>> product, = template.products
Sale with manual invoice method::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.invoice_method = 'manual'
>>> line = sale.lines.new()
>>> line.product = product
>>> line.quantity = 10
>>> sale.click('quote')
>>> sale.click('confirm')
>>> sale.state
'processing'
>>> sale.invoice_state
'none'
>>> len(sale.invoices)
0
Manually create an invoice::
>>> sale.click('manual_invoice')
>>> sale.state
'processing'
>>> sale.invoice_state
'pending'
Change quantity on invoice and create a new invoice::
>>> invoice, = sale.invoices
>>> line, = invoice.lines
>>> line.quantity = 5
>>> invoice.save()
>>> len(sale.invoices)
1
>>> sale.click('manual_invoice')
>>> len(sale.invoices)
2

View File

@@ -0,0 +1,76 @@
=============================
Sale Manual Shipment 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
Activate modules::
>>> config = activate_modules('sale', create_company)
>>> Party = Model.get('party.party')
>>> ProductCategory = Model.get('product.category')
>>> ProductTemplate = Model.get('product.template')
>>> ProductUom = Model.get('product.uom')
>>> Sale = Model.get('sale.sale')
Create party::
>>> customer = Party(name="Customer")
>>> customer.save()
Create product::
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> template = ProductTemplate()
>>> template.name = 'product'
>>> template.default_uom = unit
>>> template.type = 'goods'
>>> template.salable = True
>>> template.list_price = Decimal('10')
>>> template.save()
>>> product, = template.products
Sale with manual shipment method::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.shipment_method = 'manual'
>>> sale.invoice_method = 'manual' # no need for accounting
>>> line = sale.lines.new()
>>> line.product = product
>>> line.quantity = 10
>>> sale.click('quote')
>>> sale.click('confirm')
>>> sale.state
'processing'
>>> len(sale.shipments)
0
Manually create a shipment::
>>> sale.click('manual_shipment')
>>> sale.state
'processing'
>>> sale.shipment_state
'waiting'
Change quantity on shipment and create a new shipment::
>>> shipment, = sale.shipments
>>> move, = shipment.outgoing_moves
>>> move.quantity = 5
>>> shipment.save()
>>> len(sale.shipments)
1
>>> sale.click('manual_shipment')
>>> len(sale.shipments)
2

View File

@@ -0,0 +1,94 @@
===========================
Sale 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('sale', create_company, create_chart)
Get accounts::
>>> accounts = get_accounts()
>>> revenue = accounts['revenue']
Create tax and tax rule::
>>> tax = create_tax(Decimal('.10'))
>>> tax.save()
>>> other_tax = create_tax(Decimal('.05'))
>>> other_tax.save()
>>> TaxRule = Model.get('account.tax.rule')
>>> foreign = TaxRule(name='Foreign Customers')
>>> foreign_tax = foreign.lines.new()
>>> foreign_tax.origin_tax = tax
>>> foreign_tax.tax = other_tax
>>> foreign.save()
Create account categories::
>>> ProductCategory = Model.get('product.category')
>>> account_category = ProductCategory(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_revenue = revenue
>>> account_category.customer_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.salable = True
>>> template.list_price = Decimal('10')
>>> template.account_category = account_category
>>> template.save()
>>> product, = template.products
Create parties::
>>> Party = Model.get('party.party')
>>> customer = Party(name='Customer')
>>> customer.save()
>>> another = Party(name='Another Customer', customer_tax_rule=foreign)
>>> another.save()
Create a sale with a line::
>>> Sale = Model.get('sale.sale')
>>> sale = Sale()
>>> sale.party = customer
>>> sale_line = sale.lines.new()
>>> sale_line.product = product
>>> sale_line.quantity = 3
>>> sale_line_comment = sale.lines.new(type='comment')
>>> sale.save()
>>> sale.untaxed_amount, sale.tax_amount, sale.total_amount
(Decimal('30.00'), Decimal('3.00'), Decimal('33.00'))
Change the party::
>>> modify_header = sale.click('modify_header')
>>> assertEqual(modify_header.form.party, customer)
>>> modify_header.form.party = another
>>> modify_header.execute('modify')
>>> sale.party.name
'Another Customer'
>>> sale.untaxed_amount, sale.tax_amount, sale.total_amount
(Decimal('30.00'), Decimal('1.50'), Decimal('31.50'))

View File

@@ -0,0 +1,57 @@
=======================
Sale Quotation Scenario
=======================
Imports::
>>> import datetime as dt
>>> 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, assertEqual
>>> today = dt.date.today()
Activate modules::
>>> config = activate_modules('sale', create_company, create_chart)
>>> Cron = Model.get('ir.cron')
>>> Sale = Model.get('sale.sale')
>>> SaleConfiguration = Model.get('sale.configuration')
>>> Party = Model.get('party.party')
Set quotation validity to 1 week::
>>> sale_configuration = SaleConfiguration(1)
>>> sale_configuration.sale_quotation_validity = dt.timedelta(weeks=1)
>>> sale_configuration.save()
Create customer::
>>> customer = Party(name="Customer")
>>> customer.save()
Create a quotation::
>>> sale = Sale(party=customer)
>>> sale.click('quote')
>>> sale.state
'quotation'
>>> assertEqual(sale.quotation_date, today)
>>> assertEqual(sale.quotation_validity, dt.timedelta(weeks=1))
>>> assertEqual(sale.quotation_expire, today + dt.timedelta(weeks=1))
Expire quotation::
>>> sale.quotation_date = today - dt.timedelta(weeks=2)
>>> sale.save()
>>> cron, = Cron.find(
... [('method', '=', 'sale.sale|cancel_expired_quotation')], limit=1)
>>> cron.click('run_once')
>>> sale.reload()
>>> sale.state
'cancelled'

View File

@@ -0,0 +1,315 @@
=======================
Sale 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('sale', create_company, create_chart)
Create fiscal year::
>>> fiscalyear = set_fiscalyear_invoice_sequences(create_fiscalyear())
>>> fiscalyear.click('create_period')
Get accounts::
>>> accounts = get_accounts()
>>> revenue = accounts['revenue']
>>> expense = accounts['expense']
Create countries::
>>> Region = Model.get('country.region')
>>> Country = Model.get('country.country')
>>> Subdivision = Model.get('country.subdivision')
>>> north_america, = Region.find([('code_numeric', '=', '021')])
>>> country_us = Country(name="United States", region=north_america)
>>> country_us.save()
>>> california = Subdivision(
... name="California", type='state', country=country_us)
>>> california.save()
>>> new_york = Subdivision(
... name="New York", type='state', country=country_us)
>>> new_york.save()
Create party categories::
>>> PartyCategory = Model.get('party.category')
>>> party_category_root1 = PartyCategory(name="Root1")
>>> party_category_root1.save()
>>> party_category_child1 = PartyCategory(
... name="Child1", parent=party_category_root1)
>>> party_category_child1.save()
>>> party_category_child2 = PartyCategory(
... name="Child2", parent=party_category_root1)
>>> party_category_child2.save()
>>> party_category_root2 = PartyCategory(name="Root2")
>>> party_category_root2.save()
Create parties::
>>> Party = Model.get('party.party')
>>> customer1 = Party(name='Customer1')
>>> customer1.categories.append(PartyCategory(party_category_child1.id))
>>> customer1.categories.append(PartyCategory(party_category_root2.id))
>>> address, = customer1.addresses
>>> address.country = country_us
>>> address.subdivision = california
>>> customer1.save()
>>> customer2 = Party(name='Customer2')
>>> customer2.categories.append(PartyCategory(party_category_child2.id))
>>> address, = customer2.addresses
>>> address.country = country_us
>>> address.subdivision = new_york
>>> customer2.save()
Create account categories::
>>> Category = Model.get('product.category')
>>> account_category = Category(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_revenue = revenue
>>> account_category.save()
Create products::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> ProductTemplate = Model.get('product.template')
>>> template1 = ProductTemplate()
>>> template1.name = "Product1"
>>> template1.default_uom = unit
>>> template1.type = 'service'
>>> template1.salable = True
>>> template1.list_price = Decimal('10')
>>> template1.account_category = account_category
>>> template1.save()
>>> product1, = template1.products
>>> template2, = template1.duplicate(default={'name': "Product2"})
>>> template2.account_category = account_category
>>> template2.save()
>>> product2, = template2.products
>>> category_root1 = Category(name="Root1")
>>> category_root1.save()
>>> category_child1 = Category(name="Child1", parent=category_root1)
>>> category_child1.save()
>>> category_child2 = Category(name="Child2", parent=category_root1)
>>> category_child2.save()
>>> category_root2 = Category(name="Root2")
>>> category_root2.save()
>>> template1.categories.append(Category(category_child1.id))
>>> template1.categories.append(Category(category_root2.id))
>>> template1.save()
>>> template2.categories.append(Category(category_child2.id))
>>> template2.save()
Create sales::
>>> Sale = Model.get('sale.sale')
>>> sale1 = Sale()
>>> sale1.party = customer1
>>> sale1.sale_date = fiscalyear.start_date
>>> line = sale1.lines.new()
>>> line.product = product1
>>> line.quantity = 2
>>> line = sale1.lines.new()
>>> line.product = product2
>>> line.quantity = 1
>>> sale1.click('quote')
>>> sale1.click('confirm')
>>> sale2 = Sale()
>>> sale2.party = customer2
>>> sale2.sale_date = fiscalyear.start_date + relativedelta(months=1)
>>> line = sale2.lines.new()
>>> line.product = product1
>>> line.quantity = 1
>>> sale2.click('quote')
>>> sale2.click('confirm')
Check sale reporting per customer::
>>> Customer = Model.get('sale.reporting.customer')
>>> CustomerTimeseries = Model.get('sale.reporting.customer.time_series')
>>> context = dict(
... from_date=fiscalyear.start_date,
... to_date=fiscalyear.end_date,
... period='month')
>>> with config.set_context(context=context):
... reports = Customer.find([])
... time_series = CustomerTimeseries.find([])
>>> len(reports)
2
>>> with config.set_context(context=context):
... assertEqual({(r.customer.id, r.number, r.revenue) for r in reports},
... {(customer1.id, 1, Decimal('30')),
... (customer2.id, 1, Decimal('10'))})
>>> len(time_series)
2
>>> with config.set_context(context=context):
... assertEqual({(r.customer.id, r.date, r.number, r.revenue)
... for r in time_series},
... {(customer1.id, sale1.sale_date.replace(day=1), 1, Decimal('30')),
... (customer2.id, sale2.sale_date.replace(day=1), 1, Decimal('10'))})
Check sale reporting per customer categories::
>>> CustomerCategory = Model.get('sale.reporting.customer.category')
>>> CustomerCategoryTimeseries = Model.get(
... 'sale.reporting.customer.category.time_series')
>>> CustomerCategoryTree = Model.get('sale.reporting.customer.category.tree')
>>> with config.set_context(context=context):
... reports = CustomerCategory.find([])
... time_series = CustomerCategoryTimeseries.find([])
... tree = CustomerCategoryTree.find([])
>>> len(reports)
3
>>> with config.set_context(context=context):
... assertEqual({(r.category.id, r.number, r.revenue) for r in reports},
... {(party_category_child1.id, 1, Decimal('30')),
... (party_category_root2.id, 1, Decimal('30')),
... (party_category_child2.id, 1, Decimal('10'))})
>>> len(time_series)
3
>>> with config.set_context(context=context):
... assertEqual({
... (r.category.id, r.date, r.number, r.revenue)
... for r in time_series},
... {
... (party_category_child1.id, sale1.sale_date.replace(day=1),
... 1, Decimal('30')),
... (party_category_root2.id, sale1.sale_date.replace(day=1),
... 1, Decimal('30')),
... (party_category_child2.id, sale2.sale_date.replace(day=1),
... 1, Decimal('10'))})
>>> len(tree)
4
>>> with config.set_context(context=context):
... assertEqual({(r.name, r.revenue) for r in tree},
... {('Root1', Decimal('40')),
... ('Child1', Decimal('30')),
... ('Child2', Decimal('10')),
... ('Root2', Decimal('30'))})
>>> child1, = CustomerCategoryTree.find([('rec_name', '=', 'Child1')])
>>> child1.rec_name
'Child1'
Check sale reporting per product::
>>> Product = Model.get('sale.reporting.product')
>>> ProductTimeseries = Model.get('sale.reporting.product.time_series')
>>> 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.revenue) for r in reports},
... {(product1.id, 2, Decimal('30')),
... (product2.id, 1, Decimal('10'))})
>>> len(time_series)
3
>>> with config.set_context(context=context):
... assertEqual({(r.product.id, r.date, r.number, r.revenue)
... for r in time_series},
... {(product1.id, sale1.sale_date.replace(day=1), 1, Decimal('20')),
... (product2.id, sale1.sale_date.replace(day=1), 1, Decimal('10')),
... (product1.id, sale2.sale_date.replace(day=1), 1, Decimal('10'))})
Check sale reporting per product categories::
>>> ProductCategory = Model.get('sale.reporting.product.category')
>>> ProductCategoryTimeseries = Model.get(
... 'sale.reporting.product.category.time_series')
>>> ProductCategoryTree = Model.get('sale.reporting.product.category.tree')
>>> with config.set_context(context=context):
... reports = ProductCategory.find([])
... time_series = ProductCategoryTimeseries.find([])
... tree = ProductCategoryTree.find([])
>>> len(reports)
4
>>> with config.set_context(context=context):
... assertEqual({(r.category.id, r.number, r.revenue) for r in reports},
... {(category_child1.id, 2, Decimal('30')),
... (category_root2.id, 2, Decimal('30')),
... (category_child2.id, 1, Decimal('10')),
... (account_category.id, 2, Decimal('40'))})
>>> len(time_series)
7
>>> with config.set_context(context=context):
... assertEqual({(r.category.id, r.date, r.number, r.revenue)
... for r in time_series},
... {(category_child1.id, sale1.sale_date.replace(day=1), 1, Decimal('20')),
... (category_root2.id, sale1.sale_date.replace(day=1), 1, Decimal('20')),
... (category_child2.id, sale1.sale_date.replace(day=1), 1, Decimal('10')),
... (category_child1.id, sale2.sale_date.replace(day=1), 1, Decimal('10')),
... (category_root2.id, sale2.sale_date.replace(day=1), 1, Decimal('10')),
... (account_category.id, sale1.sale_date.replace(day=1), 1, Decimal('30')),
... (account_category.id, sale2.sale_date.replace(day=1), 1, Decimal('10'))})
>>> len(tree)
5
>>> with config.set_context(context=context):
... assertEqual({(r.name, r.revenue) for r in tree},
... {('Root1', Decimal('40')),
... ('Child1', Decimal('30')),
... ('Child2', Decimal('10')),
... ('Root2', Decimal('30')),
... ('Account Category', Decimal('40'))})
>>> child1, = ProductCategoryTree.find([('rec_name', '=', 'Child1')])
>>> child1.rec_name
'Child1'
Check sale reporting per countries::
>>> RegionTree = Model.get('sale.reporting.region.tree')
>>> CountryTree = Model.get('sale.reporting.country.tree')
>>> CountryTimeseries = Model.get('sale.reporting.country.time_series')
>>> SubdivisionTimeseries = Model.get(
... 'sale.reporting.country.subdivision.time_series')
>>> with config.set_context(context=context):
... region = RegionTree(north_america.id)
... countries = CountryTree.find([])
... country_time_series = CountryTimeseries.find([])
... subdivision_time_series = SubdivisionTimeseries.find([])
>>> region.revenue
Decimal('40.00')
>>> region.parent.revenue
Decimal('40.00')
>>> len(countries)
3
>>> with config.set_context(context=context):
... sorted((c.region, c.number, c.revenue) for c in countries)
[('California', 1, Decimal('30.00')), ('New York', 1, Decimal('10.00')), ('United States', 2, Decimal('40.00'))]
>>> len(country_time_series)
2
>>> with config.set_context(context=context):
... assertEqual({(r.country.id, r.date, r.number, r.revenue)
... for r in country_time_series},
... {(country_us.id, sale1.sale_date.replace(day=1), 1, Decimal('30')),
... (country_us.id, sale2.sale_date.replace(day=1), 1, Decimal('10'))})
>>> len(subdivision_time_series)
2
>>> with config.set_context(context=context):
... assertEqual({(r.subdivision.id, r.date, r.number, r.revenue)
... for r in subdivision_time_series},
... {(california.id, sale1.sale_date.replace(day=1), 1, Decimal('30')),
... (new_york.id, sale2.sale_date.replace(day=1), 1, Decimal('10'))})

View File

@@ -0,0 +1,71 @@
# 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 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 SaleTestCase(
PartyCompanyCheckEraseMixin, PartyCheckReplaceMixin, CompanyTestMixin,
ModuleTestCase):
'Test Sale module'
module = 'sale'
@with_transaction()
def test_sale_price(self):
"Test sale price"
pool = Pool()
Account = pool.get('account.account')
Template = pool.get('product.template')
Product = pool.get('product.product')
Uom = pool.get('product.uom')
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')])
pound, = Uom.search([('name', '=', 'Pound')])
template, = Template.create([{
'name': 'Product',
'default_uom': g.id,
'sale_uom': kg.id,
'list_price': Decimal(5),
'products': [('create', [{}])],
}])
product, = template.products
prices = Product.get_sale_price([product], quantity=100)
self.assertEqual(prices, {product.id: Decimal(5000)})
prices = Product.get_sale_price([product], quantity=1500)
self.assertEqual(prices, {product.id: Decimal(5000)})
with Transaction().set_context(uom=pound.id):
prices = Product.get_sale_price([product], quantity=0.5)
self.assertEqual(prices, {product.id: Decimal('2267.9618')})
prices = Product.get_sale_price([product], quantity=1.5)
self.assertEqual(prices, {product.id: Decimal('2267.9618')})
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)

87
modules/sale/tryton.cfg Normal file
View File

@@ -0,0 +1,87 @@
[tryton]
version=7.8.1
depends:
account
account_invoice
account_invoice_stock
account_product
company
country
currency
ir
party
product
res
stock
xml:
sale.xml
configuration.xml
sale_reporting.xml
party.xml
stock.xml
product.xml
invoice.xml
message.xml
[register]
model:
ir.Cron
party.Party
party.PartyCustomerCurrency
party.PartySaleMethod
product.Configuration
product.DefaultLeadTime
product.Template
product.ProductLeadTime
product.Product
product.SaleContext
stock.Move
stock.ShipmentOut
stock.ShipmentOutReturn
invoice.Invoice
invoice.Line
configuration.Configuration
configuration.ConfigurationSequence
configuration.ConfigurationSaleMethod
configuration.ConfigurationQuotation
sale.Sale
sale.SaleIgnoredInvoice
sale.SaleRecreatedInvoice
sale.SaleLine
sale.SaleLineTax
sale.SaleLineIgnoredMove
sale.SaleLineRecreatedMove
sale.HandleShipmentExceptionAsk
sale.HandleInvoiceExceptionAsk
sale.ReturnSaleStart
sale_reporting.Context
sale_reporting.Main
sale_reporting.MainTimeseries
sale_reporting.Customer
sale_reporting.CustomerTimeseries
sale_reporting.CustomerCategory
sale_reporting.CustomerCategoryTimeseries
sale_reporting.CustomerCategoryTree
sale_reporting.Product
sale_reporting.ProductTimeseries
sale_reporting.ProductCategory
sale_reporting.ProductCategoryTimeseries
sale_reporting.ProductCategoryTree
sale_reporting.RegionTree
sale_reporting.Country
sale_reporting.CountryTimeseries
sale_reporting.Subdivision
sale_reporting.SubdivisionTimeseries
sale_reporting.CountryTree
wizard:
party.Replace
party.Erase
sale.HandleShipmentException
sale.HandleInvoiceException
sale.ReturnSale
sale.ModifyHeader
sale.OpenProduct
sale_reporting.OpenRegionTree
sale_reporting.OpenCountryTree
report:
sale.SaleReport

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>
<label name="sale_sequence"/>
<field name="sale_sequence"/>
<newline />
<label name="sale_invoice_method" />
<field name="sale_invoice_method" />
<label name="sale_shipment_method" />
<field name="sale_shipment_method" />
<newline/>
<label name="sale_quotation_validity"/>
<field name="sale_quotation_validity"/>
<label name="sale_process_after"/>
<field name="sale_process_after"/>
</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. -->
<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,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="sale_exception_state"/>
<button name="cancel" multiple="1"/>
<button name="draft" multiple="1"/>
</tree>

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-sale" name="sale.act_sale_relate" empty="hide"/>
<link icon="tryton-sale" name="sale.act_sale_line_relate" empty="hide"/>
</xpath>
<xpath expr="/form/notebook" position="inside">
<page id="sale" string="Sale">
<label name="sale_invoice_method"/>
<field name="sale_invoice_method"/>
<label name="sale_shipment_method"/>
<field name="sale_shipment_method"/>
<label name="customer_currency"/>
<field name="customer_currency"/>
<newline/>
</page>
</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="/form" position="inside">
<label name="default_lead_time"/>
<field name="default_lead_time"/>
</xpath>
</data>

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. -->
<tree>
<field name="code" optional="0"/>
<field name="name" expand="1"/>
<field name="sale_price_uom"/>
<field name="list_price_uom" optional="1"/>
<field name="cost_price_uom" optional="1"/>
<field name="quantity" symbol="default_uom" optional="0"/>
<field name="forecast_quantity" symbol="default_uom" optional="1"/>
</tree>

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. -->
<form>
<group colspan="2" col="4" id="fields">
<label name="company"/>
<field name="company"/>
<label name="currency"/>
<field name="currency"/>
<label name="sale_date"/>
<field name="sale_date"/>
<label name="quantity"/>
<field name="quantity"/>
<label name="customer"/>
<field name="customer"/>
</group>
<field name="locations" colspan="2" yexpand="0"/>
<field name="stock_date_end" invisible="1" colspan="4"/>
</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. -->
<form col="2">
<image name="tryton-warning" xexpand="0" xfill="0"/>
<label
string="Are you sure to return these/this sale(s)?" id="return"
yalign="0.5" xalign="0.0" xexpand="1"/>
</form>

View File

@@ -0,0 +1,111 @@
<?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="shipment_party"/>
<field name="shipment_party"/>
<label name="shipment_address"/>
<field name="shipment_address" colspan="3"/>
<label name="description"/>
<field name="description" colspan="3"/>
<label name="reference"/>
<field name="reference"/>
<notebook colspan="6">
<page string="Sale" id="sale" col="6">
<label name="sale_date"/>
<field name="sale_date"/>
<label name="payment_term"/>
<field name="payment_term"/>
<label name="currency"/>
<field name="currency"/>
<label name="quotation_date"/>
<field name="quotation_date"/>
<label name="quotation_validity"/>
<field name="quotation_validity"/>
<label name="quotation_expire"/>
<field name="quotation_expire"/>
<label name="warehouse"/>
<field name="warehouse"/>
<label name="shipping_date"/>
<field name="shipping_date"/>
<field name="lines" colspan="6" view_ids="sale.sale_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="10" 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"/>
<newline/>
<label name="tax_amount" xalign="1.0" xexpand="1" xfill="0"/>
<field name="tax_amount" xalign="1.0" xexpand="0"/>
<newline/>
<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="other">
<label name="company"/>
<field name="company"/>
<label name="origin"/>
<field name="origin"/>
<label name="invoice_method"/>
<field name="invoice_method"/>
<label name="shipment_method"/>
<field name="shipment_method"/>
<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-out" name="sale.act_shipment_form"/>
<link icon="tryton-account" name="sale.act_invoice_form"/>
<link icon="tryton-shipment-in" name="sale.act_return_form"/>
</group>
<group col="-1" colspan="3" id="buttons">
<button name="cancel" icon="tryton-cancel"/>
<button name="modify_header" icon="tryton-launch"/>
<button name="draft"/>
<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-forward"/>
<button name="process"/>
<button name="manual_invoice" icon="tryton-forward"/>
<button name="manual_shipment" icon="tryton-forward"/>
</group>
<field name="party_lang" invisible="1" colspan="6"/>
</form>

View File

@@ -0,0 +1,45 @@
<?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="sale"/>
<field name="sale" 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="sale.product_view_list_sale_line"/>
<newline/>
<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 name="shipping_date"/>
<field name="shipping_date"/>
<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,18 @@
<?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="sale" expand="1"/>
<field name="customer" expand="1" optional="0"/>
<field name="sale_date" optional="1"/>
<field name="type" optional="1"/>
<field name="product" expand="1" optional="0"/>
<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="sale_state"/>
</tree>

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 sequence="sequence">
<field name="sale" expand="1"/>
<field name="type" optional="1"/>
<field name="product" expand="1" optional="0"/>
<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>
<label name="from_date"/>
<group id="dates" col="-1">
<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,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. -->
<tree keyword_open="1">
<field name="region" expand="1"/>
<field name="number"/>
<field name="revenue"/>
<field name="revenue_trend" expand="1"/>
</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. -->
<data>
<xpath expr="//field[@name='number']" position="before">
<field name="category" expand="1"/>
</xpath>
<xpath expr="//field[@name='number']" position="replace">
<field name="number"/>
</xpath>
<xpath expr="//field[@name='revenue']" position="replace">
<field name="revenue"/>
</xpath>
</data>

View File

@@ -0,0 +1,7 @@
<?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="name" expand="1"/>
<field name="revenue"/>
</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="customer"/>
</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="customer"/>
</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='number']" position="before">
<field name="customer" expand="1"/>
</xpath>
</data>

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,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="revenue"/>
</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="revenue" sum="1"/>
<field name="revenue_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="number"/>
</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="revenue"/>
</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="revenue" sum="1"/>
</tree>

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