Initial import from Docker volume

This commit is contained in:
root
2025-12-26 13:11:43 +00:00
commit 4998dc066a
13336 changed files with 1767801 additions and 0 deletions

73
modules/sale/__init__.py Executable file
View File

@@ -0,0 +1,73 @@
# 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 Pool
from . import (
configuration, invoice, party, product, sale, sale_reporting, stock)
def register():
Pool.register(
stock.Move,
sale.Sale,
sale.SaleIgnoredInvoice,
sale.SaleRecreatedInvoice,
sale.SaleLine,
sale.SaleInvoiceLine,
sale.SaleLineTax,
sale.SaleLineIgnoredMove,
sale.SaleLineRecreatedMove,
party.Party,
party.PartyCustomerCurrency,
party.PartySaleMethod,
product.Configuration,
product.DefaultLeadTime,
product.Template,
product.ProductLeadTime,
product.Product,
product.SaleContext,
stock.ShipmentOut,
stock.ShipmentOutReturn,
sale.HandleShipmentExceptionAsk,
sale.HandleInvoiceExceptionAsk,
sale.ReturnSaleStart,
configuration.Configuration,
configuration.ConfigurationSequence,
configuration.ConfigurationSaleMethod,
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,
invoice.Invoice,
invoice.Line,
module='sale', type_='model')
Pool.register(
sale.OpenCustomer,
sale.HandleShipmentException,
sale.HandleInvoiceException,
sale.ReturnSale,
sale.ModifyHeader,
party.Replace,
party.Erase,
sale_reporting.OpenRegionTree,
sale_reporting.OpenCountryTree,
module='sale', type_='wizard')
Pool.register(
sale.SaleReport,
module='sale', type_='report')

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.

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.

107
modules/sale/configuration.py Executable file
View File

@@ -0,0 +1,107 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.model import (
ModelSingleton, ModelSQL, ModelView, ValueMixin, fields)
from trytond.modules.company.model import (
CompanyMultiValueMixin, CompanyValueMixin)
from trytond.pool import Pool
from trytond.pyson import Eval, Id, TimeDelta
sale_invoice_method = fields.Selection(
'get_sale_invoice_methods', "Sale Invoice Method")
sale_shipment_method = fields.Selection(
'get_sale_shipment_methods', "Sale Shipment Method")
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):
'Sale Configuration'
__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_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')
return super(Configuration, cls).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):
"Sale Configuration Sequence"
__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):
"Sale Configuration Sale Method"
__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'

56
modules/sale/configuration.xml Executable file
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>

25
modules/sale/exceptions.py Executable file
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

163
modules/sale/invoice.py Executable file
View File

@@ -0,0 +1,163 @@
# 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)
func(cls, invoices)
if sales:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Sale.__queue__.process(sales)
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:])]
@classmethod
@process_sale
def delete(cls, invoices):
super(Invoice, cls).delete(invoices)
@classmethod
@process_sale
def _post(cls, invoices):
super()._post(invoices)
@classmethod
@process_sale
def paid(cls, invoices):
super(Invoice, cls).paid(invoices)
@classmethod
@process_sale
def cancel(cls, invoices):
super(Invoice, cls).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(Invoice, cls).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):
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 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)
super().delete(lines)
if sales:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Sale.__queue__.process(sales)

38
modules/sale/invoice.xml Executable file
View File

@@ -0,0 +1,38 @@
<?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="[If(Eval('active_ids', []) == [Eval('active_id')], ('lines.origin.sale.id', '=', Eval('active_id'), 'sale.line'), ('lines.origin.sale.id', 'in', Eval('active_ids'), 'sale.line'))]"
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.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>

1737
modules/sale/locale/bg.po Executable file

File diff suppressed because it is too large Load Diff

1647
modules/sale/locale/ca.po Executable file

File diff suppressed because it is too large Load Diff

1698
modules/sale/locale/cs.po Executable file

File diff suppressed because it is too large Load Diff

1653
modules/sale/locale/de.po Executable file

File diff suppressed because it is too large Load Diff

1646
modules/sale/locale/es.po Executable file

File diff suppressed because it is too large Load Diff

1619
modules/sale/locale/es_419.po Executable file

File diff suppressed because it is too large Load Diff

1734
modules/sale/locale/et.po Executable file

File diff suppressed because it is too large Load Diff

1747
modules/sale/locale/fa.po Executable file

File diff suppressed because it is too large Load Diff

1695
modules/sale/locale/fi.po Executable file

File diff suppressed because it is too large Load Diff

1647
modules/sale/locale/fr.po Executable file

File diff suppressed because it is too large Load Diff

1704
modules/sale/locale/hu.po Executable file

File diff suppressed because it is too large Load Diff

1691
modules/sale/locale/id.po Executable file

File diff suppressed because it is too large Load Diff

1727
modules/sale/locale/it.po Executable file

File diff suppressed because it is too large Load Diff

1792
modules/sale/locale/lo.po Executable file

File diff suppressed because it is too large Load Diff

1733
modules/sale/locale/lt.po Executable file

File diff suppressed because it is too large Load Diff

1648
modules/sale/locale/nl.po Executable file

File diff suppressed because it is too large Load Diff

1720
modules/sale/locale/pl.po Executable file

File diff suppressed because it is too large Load Diff

1732
modules/sale/locale/pt.po Executable file

File diff suppressed because it is too large Load Diff

1648
modules/sale/locale/ro.po Executable file

File diff suppressed because it is too large Load Diff

1738
modules/sale/locale/ru.po Executable file

File diff suppressed because it is too large Load Diff

1720
modules/sale/locale/sl.po Executable file

File diff suppressed because it is too large Load Diff

1694
modules/sale/locale/tr.po Executable file

File diff suppressed because it is too large Load Diff

1603
modules/sale/locale/uk.po Executable file

File diff suppressed because it is too large Load Diff

1714
modules/sale/locale/zh_CN.po Executable file

File diff suppressed because it is too large Load Diff

79
modules/sale/message.xml Executable 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.</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>

125
modules/sale/party.py Executable file
View File

@@ -0,0 +1,125 @@
# 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):
"Party Sale Method"
__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):
"Party Customer Currency"
__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))

66
modules/sale/party.xml Executable file
View File

@@ -0,0 +1,66 @@
<?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.action.wizard" id="act_open_customer">
<field name="name">Parties associated to Sales</field>
<field name="wiz_name">sale.open_customer</field>
</record>
<menuitem
name="Associated to Sales"
parent="party.menu_party_form"
action="act_open_customer"
sequence="50"
id="menu_customer"
icon="tryton-list"/>
<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>

269
modules/sale/product.py Executable file
View File

@@ -0,0 +1,269 @@
# 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):
"Product Default Lead Time"
__name__ = 'product.configuration.default_lead_time'
default_lead_time = default_lead_time
@classmethod
def default_default_lead_time(cls, **pattern):
return datetime.timedelta(0)
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')),
],
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(Template, self).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(Template, cls).view_attributes() + [
('//page[@id="customers"]', 'states', {
'invisible': ~Eval('salable'),
})]
class ProductLeadTime(ModelSQL, ValueMixin):
"Product Lead Time"
__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):
"Product Sale Context"
__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')
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')
company_id = cls.default_company()
if company_id:
company = Company(company_id)
return company.currency.id
@fields.depends('sale_date')
def on_change_with_stock_date_end(self, name=None):
return self.sale_date

62
modules/sale/product.xml Executable 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>

891
modules/sale/sale.fodt Executable file
View File

@@ -0,0 +1,891 @@
<?xml version="1.0" encoding="UTF-8"?>
<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
<office:meta><meta:generator>LibreOffice/7.4.4.2$Linux_X86_64 LibreOffice_project/40$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="5" meta:paragraph-count="89" meta:word-count="210" meta:character-count="2408" meta:non-whitespace-character-count="2287"/><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">22013</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">48129</config:config-item>
<config:config-item config:name="ViewAreaHeight" config:type="long">24026</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">5225</config:config-item>
<config:config-item config:name="ViewTop" config:type="long">34452</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">22013</config:config-item>
<config:config-item config:name="VisibleRight" config:type="long">48128</config:config-item>
<config:config-item config:name="VisibleBottom" config:type="long">46038</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="HideWhitespace" config:type="boolean">false</config:config-item>
<config:config-item config:name="AnchoredTextOverflowLegacy" 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="PrintProspect" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintReversed" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintLeftPages" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintControls" config:type="boolean">true</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="PrintBlackFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintAnnotationMode" config:type="short">0</config:config-item>
<config:config-item config:name="PrintTextPlaceholder" 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="ProtectBookmarks" 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="DisableOffPagePositioning" config:type="boolean">true</config:config-item>
<config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">true</config:config-item>
<config:config-item config:name="PropLineSpacingShrinksFirstLine" 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="GutterAtTop" config:type="boolean">false</config:config-item>
<config:config-item config:name="TreatSingleColumnBreakAsPageBreak" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmbedSystemFonts" 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="EmbedLatinScriptFonts" config:type="boolean">true</config:config-item>
<config:config-item config:name="EmbedOnlyUsedFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="WordLikeWrapForAsCharFlys" 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="EmbedFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="ClippedPictures" config:type="boolean">false</config:config-item>
<config:config-item config:name="FloattableNomargins" config:type="boolean">false</config:config-item>
<config:config-item config:name="UnbreakableNumberings" config:type="boolean">false</config:config-item>
<config:config-item config:name="HeaderSpacingBelowLastPara" 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="UseOldPrinterMetrics" 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="TabsRelativeToIndent" config:type="boolean">true</config:config-item>
<config:config-item config:name="UseOldNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintPaperFromSetup" config:type="boolean">false</config:config-item>
<config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item>
<config:config-item config:name="CurrentDatabaseCommandType" config:type="int">0</config:config-item>
<config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item>
<config:config-item config:name="AddParaSpacingToTableCells" config:type="boolean">true</config:config-item>
<config:config-item config:name="FrameAutowidthWithMorePara" config:type="boolean">false</config:config-item>
<config:config-item config:name="CurrentDatabaseCommand" config:type="string"/>
<config:config-item config:name="PrinterIndependentLayout" config:type="string">high-resolution</config:config-item>
<config:config-item config:name="ApplyUserData" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintFaxName" config:type="string"/>
<config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/>
<config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" 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="SaveThumbnail" config:type="boolean">true</config:config-item>
<config:config-item config:name="UseFormerTextWrapping" 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="AddExternalLeading" 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="StylesNoDefault" config:type="boolean">false</config:config-item>
<config:config-item config:name="ChartAutoUpdate" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrinterSetup" config:type="base64Binary"/>
<config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item>
<config:config-item config:name="Rsid" config:type="int">1753347</config:config-item>
<config:config-item config:name="EmbeddedDatabaseName" config:type="string"/>
<config:config-item config:name="FieldAutoUpdate" 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="FootnoteInColumnToPageEnd" config:type="boolean">true</config:config-item>
<config:config-item config:name="AlignTabStopPosition" config:type="boolean">true</config:config-item>
<config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
<config:config-item config:name="PrinterName" config:type="string"/>
<config:config-item config:name="SaveGlobalDocumentLinks" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrinterPaperFromSetup" config:type="boolean">false</config:config-item>
<config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddParaLineSpacingToTableCells" config:type="boolean">false</config:config-item>
<config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintGraphics" 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="SurroundTextWrapSmall" 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="MsWordCompTrailingBlanks" 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="TabOverSpacing" 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="NoNumberingShowFollowBy" config:type="boolean">false</config:config-item>
<config:config-item config:name="RedlineProtectionKey" config:type="base64Binary"/>
<config:config-item config:name="DoNotJustifyLinesWithManualBreak" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintProspectRTL" 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="DoNotResetParaAttrsForNumFont" 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="IgnoreTabsAndBlanksForLineCalculation" 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="DoNotCaptureDrawObjsOnPage" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddVerticalFrameOffsets" 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="IsLabelDocument" 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="RsidRoot" config:type="int">204959</config:config-item>
<config:config-item config:name="PrintHiddenText" config:type="boolean">false</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="BackgroundParaOverDrawings" 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="MathBaselineAlignment" config:type="boolean">false</config:config-item>
<config:config-item config:name="SmallCapsPercentage66" config:type="boolean">true</config:config-item>
<config:config-item config:name="CollapseEmptyCellPara" config:type="boolean">true</config:config-item>
<config:config-item config:name="TabOverflow" config:type="boolean">false</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%"/>
<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:flow-with-text="false"/>
<style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" style:font-independent-line-spacing="false">
<style:tab-stops/>
</style:paragraph-properties>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" loext:color-lum-mod="100%" loext:color-lum-off="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="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>
<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"/>
</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="center" style:justify-single-word="false"/>
<style:text-properties officeooo:paragraph-rsid="00066dfa"/>
</style:style>
<style:style style:name="P5" 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="P6" 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="P7" 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="P8" 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="P9" 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="P10" 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="P11" 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="P12" 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="P13" 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="P14" 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="P15" 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="P16" 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="P17" 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="P18" 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="P19" 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="P20" 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="P21" 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="P22" 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="P23" 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="P24" 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="P25" 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="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:break-before="column" fo:keep-with-next="auto"/>
<style:text-properties officeooo:paragraph-rsid="001ac103"/>
</style:style>
<style:style style:name="P27" 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="P28" 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="P29" 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="P30" 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="T1" style:family="text">
<style:text-properties officeooo:rsid="001ac103"/>
</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;company.rec_name if company else &apos;&apos;&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="P19"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;sale in records&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P24"/>
<text:p text:style-name="P9"><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="P9"><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="P8"><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="P8"><text:placeholder text:placeholder-type="text">&lt;line&gt;</text:placeholder></text:p>
<text:p text:style-name="P8"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
<text:p text:style-name="P8"><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="P8"><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="P8"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text">&lt;choose test=&quot;&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P17"><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="P23">Draft Sale Order</text:p>
<text:p text:style-name="P17"><text:placeholder text:placeholder-type="text">&lt;/when&gt;</text:placeholder></text:p>
<text:p text:style-name="P17"><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="P23">Quotation N°: <text:placeholder text:placeholder-type="text">&lt;sale.full_number&gt;</text:placeholder></text:p>
<text:p text:style-name="P17"><text:placeholder text:placeholder-type="text">&lt;/when&gt;</text:placeholder></text:p>
<text:p text:style-name="P17"><text:placeholder text:placeholder-type="text">&lt;otherwise test=&quot;&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P23">Sale Order N°: <text:placeholder text:placeholder-type="text">&lt;sale.full_number&gt;</text:placeholder></text:p>
<text:p text:style-name="P17"><text:placeholder text:placeholder-type="text">&lt;/otherwise&gt;</text:placeholder></text:p>
<text:p text:style-name="P28"><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="P29">Description: <text:placeholder text:placeholder-type="text">&lt;sale.description or &apos;&apos;&gt;</text:placeholder></text:p>
<text:p text:style-name="P30">Reference: <text:placeholder text:placeholder-type="text">&lt;sale.reference or &apos;&apos;&gt;</text:placeholder></text:p>
<text:p text:style-name="P25">Date: <text:placeholder text:placeholder-type="text">&lt;format_date(sale.sale_date or today, sale.party.lang)&gt;</text:placeholder></text:p>
<text:p text:style-name="P26"><text:soft-page-break/><text:span text:style-name="T1">Delivery Address:</text:span></text:p>
<text:p text:style-name="P27"><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="P27"><text:placeholder text:placeholder-type="text">&lt;line&gt;</text:placeholder></text:p>
<text:p text:style-name="P27"><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="P20"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;line.product_name&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P20"><text:placeholder text:placeholder-type="text">&lt;line.product_name&gt;</text:placeholder></text:p>
<text:p text:style-name="P20"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
<text:p text:style-name="P20"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;line.description&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P20"><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="P22"><text:placeholder text:placeholder-type="text">&lt;description&gt;</text:placeholder></text:p>
<text:p text:style-name="P22"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
<text:p text:style-name="P22"><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="P13"><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="P14"><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="P6"><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="P6"><text:placeholder text:placeholder-type="text">&lt;tax.description&gt;</text:placeholder></text:p>
<text:p text:style-name="P6"><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="P13"><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:placeholder text:placeholder-type="text">&lt;description&gt;</text:placeholder></text:p>
<text:p text:style-name="P16"><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="P13"><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>
<text:soft-page-break/>
<table:table-row>
<table:table-cell table:style-name="Table1.A2" table:number-columns-spanned="5" office:value-type="string">
<text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;when test=&quot;line.type == &apos;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="P20"><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="P20"><text:placeholder text:placeholder-type="text">&lt;description&gt;</text:placeholder></text:p>
<text:p text:style-name="P21"><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>
<table:table-row>
<table:table-cell table:style-name="Table1.A2" table:number-columns-spanned="5" office:value-type="string">
<text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
</table:table>
<text:p text:style-name="Text_20_body"/>
<table:table table:name="Table2" table:style-name="Table2">
<table:table-column table:style-name="Table2.A"/>
<table:table-column table:style-name="Table2.B"/>
<text:soft-page-break/>
<table:table-row>
<table:table-cell office:value-type="string">
<text:p text:style-name="Table_20_Contents"/>
</table:table-cell>
<table:table-cell office:value-type="string">
<table:table table:name="Table4" table:style-name="Table4">
<table:table-column table:style-name="Table4.A"/>
<table:table-column table:style-name="Table4.B"/>
<table:table-row>
<table:table-cell table:style-name="Table4.A1" office:value-type="string">
<text:p text:style-name="P12">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="P13"><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="P12">Taxes:</text:p>
</table:table-cell>
<table:table-cell table:style-name="Table4.B2" office:value-type="string">
<text:p text:style-name="P13"><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="P12">Total:</text:p>
</table:table-cell>
<table:table-cell table:style-name="Table4.B2" office:value-type="string">
<text:p text:style-name="P13"><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: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>

2546
modules/sale/sale.py Executable file

File diff suppressed because it is too large Load Diff

553
modules/sale/sale.xml Executable file
View File

@@ -0,0 +1,553 @@
<?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">Validation</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">Validate</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_invoice_line_view_tree">
<field name="model">sale.invoice.line</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">sale_invoice_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>
</data>
</tryton>

1103
modules/sale/sale_reporting.py Executable file

File diff suppressed because it is too large Load Diff

1042
modules/sale/sale_reporting.xml Executable file

File diff suppressed because it is too large Load Diff

218
modules/sale/stock.py Executable file
View File

@@ -0,0 +1,218 @@
# 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)
func(cls, shipments)
if sales:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Sale.__queue__.process(sales)
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(ShipmentOut, cls).draft(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(ShipmentOut, cls).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(ShipmentOutReturn, cls).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(ShipmentOutReturn, cls).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)
func(cls, moves)
if sales:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Sale.__queue__.process(sales)
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 _get_origin(cls):
models = super(Move, cls)._get_origin()
models.append('sale.line')
return models
@classmethod
def check_origin_types(cls):
types = super(Move, cls).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(Move, self).origin_name
if isinstance(self.origin, SaleLine):
name = self.origin.sale.rec_name
return name
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
@process_sale_move
def cancel(cls, moves):
super(Move, cls).cancel(moves)
@classmethod
@process_sale_move
def delete(cls, moves):
super(Move, cls).delete(moves)

64
modules/sale/stock.xml Executable file
View File

@@ -0,0 +1,64 @@
<?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">Moves</field>
<field name="res_model">stock.move</field>
<field
name="domain"
eval="[If(Eval('active_ids', []) == [Eval('active_id')], ('sale', '=', Eval('active_id')), ('sale', 'in', Eval('active_ids')))]"
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_keyword1">
<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.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>

2
modules/sale/tests/__init__.py Executable 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.

View File

@@ -0,0 +1,709 @@
=============
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, get_company
>>> from trytond.tests.tools import activate_modules, assertEqual, set_user
Activate modules::
>>> config = activate_modules('sale')
Create company::
>>> _ = create_company()
>>> company = get_company()
Set employee::
>>> User = Model.get('res.user')
>>> Party = Model.get('party.party')
>>> Employee = Model.get('company.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(company))
>>> fiscalyear.click('create_period')
Create chart of accounts::
>>> _ = create_chart(company)
>>> accounts = get_accounts(company)
>>> 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,36 @@
=============================
Sale Default Methods Scenario
=============================
Imports::
>>> from proteus import Model
>>> from trytond.modules.company.tests.tools import create_company, get_company
>>> from trytond.tests.tools import activate_modules
Activate modules::
>>> config = activate_modules('sale')
Create company::
>>> _ = create_company()
>>> company = get_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,64 @@
=======================
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')
>>> Party = Model.get('party.party')
>>> Sale = Model.get('sale.sale')
>>> Tax = Model.get('account.tax')
Create company::
>>> _ = create_company()
Create chart of accounts::
>>> _ = create_chart()
>>> 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,57 @@
===================
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, get_company
>>> from trytond.tests.tools import activate_modules
Activate modules::
>>> config = activate_modules('sale')
Create company::
>>> _ = create_company()
>>> company = get_company()
Create chart of accounts::
>>> _ = create_chart(company)
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,106 @@
============================
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')
>>> 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 company::
>>> _ = create_company()
Create chart of accounts::
>>> _ = create_chart()
>>> 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.recreate_invoices.clear()
>>> invoice_handle_exception.execute('handle')
>>> sale.invoice_state
'none'
>>> shipment_handle_exception = sale.click('handle_shipment_exception')
>>> shipment_handle_exception.form.recreate_moves.clear()
>>> shipment_handle_exception.execute('handle')
>>> sale.shipment_state
'none'
>>> sale.state
'done'

View File

@@ -0,0 +1,95 @@
=======================================
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')
>>> 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 company::
>>> _ = create_company()
Create chart of accounts::
>>> _ = create_chart()
>>> 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.recreate_invoices.clear()
>>> invoice_handle_exception.execute('handle')
>>> sale.shipment_state
'none'
>>> sale.state
'done'

View File

@@ -0,0 +1,95 @@
========================================
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')
>>> 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 company::
>>> _ = create_company()
Create chart of accounts::
>>> _ = create_chart()
>>> 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.recreate_moves.clear()
>>> shipment_handle_exception.execute('handle')
>>> sale.shipment_state
'none'
>>> sale.state
'done'

View File

@@ -0,0 +1,95 @@
============================
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')
>>> 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 company::
>>> _ = create_company()
Create chart of accounts::
>>> _ = create_chart()
>>> 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,80 @@
=============================
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')
>>> 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 company::
>>> _ = create_company()
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,100 @@
===========================
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, get_company
>>> from trytond.tests.tools import activate_modules, assertEqual
Activate modules::
>>> config = activate_modules('sale')
Create company::
>>> _ = create_company()
>>> company = get_company()
Create chart of accounts::
>>> _ = create_chart(company)
>>> accounts = get_accounts(company)
>>> 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', company=company)
>>> 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,322 @@
=======================
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, get_company
>>> from trytond.tests.tools import activate_modules, assertEqual
Activate modules::
>>> config = activate_modules('sale')
Create company::
>>> _ = create_company()
>>> company = get_company()
Create fiscal year::
>>> fiscalyear = set_fiscalyear_invoice_sequences(
... create_fiscalyear(company))
>>> fiscalyear.click('create_period')
Create chart of accounts::
>>> _ = create_chart(company)
>>> accounts = get_accounts(company)
>>> 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,65 @@
# 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.pool import Pool
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
from trytond.transaction import Transaction
class SaleTestCase(
PartyCompanyCheckEraseMixin, 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([
('type.receivable', '=', True),
('company', '=', company.id),
])
payable, = Account.search([
('type.payable', '=', True),
('company', '=', company.id),
])
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)

24
modules/sale/tryton.cfg Executable file
View File

@@ -0,0 +1,24 @@
[tryton]
version=7.2.3
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

View File

@@ -0,0 +1,15 @@
<?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_process_after"/>
<field name="sale_process_after"/>
</form>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form col="2">
<image name="tryton-question" xexpand="0" xfill="0"/>
<label string="Choose invoices to recreate:" id="choose"
yalign="0.0" xalign="0.0" xexpand="1"/>
<field name="recreate_invoices" colspan="2" widget="multiselection"/>
</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-question" xexpand="0" xfill="0"/>
<label string="Choose move to recreate:" id="choose"
yalign="0.0" xalign="0.0" xexpand="1"/>
<field name="recreate_moves" colspan="2" widget="multiselection"/>
</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"/>
<button name="draft"/>
</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.0" xalign="0.0" xexpand="1"/>
</form>

110
modules/sale/view/sale_form.xml Executable file
View File

@@ -0,0 +1,110 @@
<?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">
<group col="4" colspan="4" id="hd" yfill="1">
<label name="party"/>
<field name="party"/>
<label name="number"/>
<field name="number"/>
<!-- <label name="contact"/>
<field name="contact"/> -->
<newline/>
<label name="reference"/>
<field name="reference"/>
<label name="sale_date"/>
<field name="sale_date"/>
<newline/>
<label name="payment_term"/>
<field name="payment_term"/>
<label name="currency"/>
<field name="currency"/>
<newline/>
<label name="certif"/>
<field name="certif"/>
</group>
<group col="2" colspan="2" id="hd" yfill="1">
<field name="viewer" widget="html_viewer" height="300" width="600"/>
</group>
<notebook colspan="6">
<page string="Sale" id="sale">
<field name="lines" colspan="4"
view_ids="sale.sale_line_view_tree_sequence"/>
<notebook colspan="6">
<page string="State" id="state">
<group col="2" colspan="2" id="states" yfill="1">
<label name="invoice_state"/>
<field name="invoice_state"/>
<label name="shipment_state"/>
<field name="shipment_state"/>
<label name="state"/>
<field name="state"/>
</group>
<group col="2" colspan="2" id="amount" yfill="1">
<label name="untaxed_amount" xalign="1.0" xexpand="1"/>
<field name="untaxed_amount" xalign="1.0" xexpand="0"/>
<label name="tax_amount" xalign="1.0" xexpand="1"/>
<field name="tax_amount" xalign="1.0" xexpand="0"/>
<label name="total_amount" xalign="1.0" xexpand="1"/>
<field name="total_amount" xalign="1.0" xexpand="0"/>
</group>
</page>
</notebook>
</page>
<page string="Invoice info" id="invoice">
<label name="invoice_party"/>
<field name="invoice_party"/>
<newline/>
<label name="invoice_address"/>
<field name="invoice_address" colspan="3"/>
<label name="shipment_party"/>
<field name="shipment_party"/>
<newline/>
<label name="shipment_address"/>
<field name="shipment_address" colspan="3"/>
<newline/>
<label name="description"/>
<field name="description" colspan="3"/>
</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="shipping_date"/>
<field name="shipping_date"/>
<newline/>
<label name="quoted_by"/>
<field name="quoted_by"/>
<label name="confirmed_by"/>
<field name="confirmed_by"/>
<separator name="comment" colspan="4"/>
<field name="comment" colspan="4"/>
</page>
<page name="invoices_ignored" col="1">
<field name="invoices_ignored"/>
</page>
</notebook>
<group id="links" col="-1" colspan="3">
<link icon="tryton-shipment-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,6 @@
<tree editable="1">
<field name="invoice_party"/>
<field name="invoice_address"/>
<field name="shipment_party"/>
<field name="shipment_address"/>
</tree>

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. -->
<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="del_period"/>
<field name="del_period"/>
<newline/>
<label name="from_del"/>
<field name="from_del"/>
<label name="to_del"/>
<field name="to_del"/>
<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 string="Invoice info" id="invoice">
<field name="invoice_info"/>
</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>

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