mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-25 01:52:05 +02:00
19.0 vanilla
This commit is contained in:
parent
79f83631d5
commit
73afc09215
6267 changed files with 1534193 additions and 1130106 deletions
|
|
@ -12,37 +12,16 @@ pip install odoo-bringout-oca-ocb-sale
|
|||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
- sales_team
|
||||
- account_payment
|
||||
- utm
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Sales
|
||||
- **Version**: 1.2
|
||||
- **Category**: Sales/Sales
|
||||
- **License**: LGPL-3
|
||||
- **Installable**: True
|
||||
|
||||
## Source
|
||||
|
||||
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `sale`.
|
||||
- Repository: https://github.com/OCA/OCB
|
||||
- Branch: 19.0
|
||||
- Path: addons/sale
|
||||
|
||||
## License
|
||||
|
||||
This package maintains the original LGPL-3 license from the upstream Odoo project.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Overview: doc/OVERVIEW.md
|
||||
- Architecture: doc/ARCHITECTURE.md
|
||||
- Models: doc/MODELS.md
|
||||
- Controllers: doc/CONTROLLERS.md
|
||||
- Wizards: doc/WIZARDS.md
|
||||
- Install: doc/INSTALL.md
|
||||
- Usage: doc/USAGE.md
|
||||
- Configuration: doc/CONFIGURATION.md
|
||||
- Dependencies: doc/DEPENDENCIES.md
|
||||
- Troubleshooting: doc/TROUBLESHOOTING.md
|
||||
- FAQ: doc/FAQ.md
|
||||
This package preserves the original LGPL-3 license.
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
[project]
|
||||
name = "odoo-bringout-oca-ocb-sale"
|
||||
version = "16.0.0"
|
||||
description = "Sales - Sales internal machinery"
|
||||
description = "Sales -
|
||||
Sales internal machinery
|
||||
"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"odoo-bringout-oca-ocb-sales_team>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-account_payment>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-utm>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-sales_team>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-account_payment>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-utm>=19.0.0",
|
||||
"requests>=2.25.1"
|
||||
]
|
||||
readme = "README.md"
|
||||
|
|
@ -18,14 +20,14 @@ classifiers = [
|
|||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
homepage = "https://github.com/bringout/odoo-bringout-oca-ocb-sale"
|
||||
repository = "https://github.com/bringout/odoo-bringout-oca-ocb-sale"
|
||||
homepage = "https://github.com/bringout/0"
|
||||
repository = "https://github.com/bringout/0"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
|
|
|
|||
|
|
@ -1,18 +1,33 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from odoo.tools.misc import str2bool
|
||||
|
||||
from . import const
|
||||
from . import controllers
|
||||
from . import models
|
||||
from . import report
|
||||
from . import wizard
|
||||
from . import populate
|
||||
|
||||
from odoo.api import Environment, SUPERUSER_ID
|
||||
|
||||
|
||||
def _synchronize_cron(cr, registry):
|
||||
env = Environment(cr, SUPERUSER_ID, {'active_test': False})
|
||||
send_invoice_cron = env.ref('sale.send_invoice_cron', raise_if_not_found=False)
|
||||
if send_invoice_cron:
|
||||
config = env['ir.config_parameter'].get_param('sale.automatic_invoice', False)
|
||||
send_invoice_cron.active = bool(config)
|
||||
def _post_init_hook(env):
|
||||
_synchronize_crons(env)
|
||||
_setup_downpayment_account(env)
|
||||
|
||||
|
||||
def _synchronize_crons(env):
|
||||
for param, cron_xmlid in const.PARAM_CRON_MAPPING.items():
|
||||
if cron := env.ref(cron_xmlid, raise_if_not_found=False):
|
||||
cron.active = str2bool(env['ir.config_parameter'].get_param(param, 'False'))
|
||||
|
||||
|
||||
def _setup_downpayment_account(env):
|
||||
for company in env.companies:
|
||||
if not company.chart_template:
|
||||
continue
|
||||
|
||||
template_data = env['account.chart.template']._get_chart_template_data(company.chart_template).get('template_data')
|
||||
if template_data and template_data.get('downpayment_account_id'):
|
||||
property_downpayment_account = env['account.chart.template'].with_company(company).ref(template_data['downpayment_account_id'], raise_if_not_found=False)
|
||||
if property_downpayment_account:
|
||||
company.downpayment_account_id = property_downpayment_account
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
|
||||
{
|
||||
'name': 'Sales',
|
||||
'version': '1.2',
|
||||
|
|
@ -25,16 +23,19 @@ This module contains all the common features of Sales Management and eCommerce.
|
|||
'report/ir_actions_report.xml',
|
||||
'report/sale_report_views.xml',
|
||||
|
||||
'data/ir_cron.xml',
|
||||
'data/ir_sequence_data.xml',
|
||||
'data/mail_activity_type_data.xml',
|
||||
'data/mail_message_subtype_data.xml',
|
||||
'data/mail_template_data.xml',
|
||||
'data/sale_data.xml',
|
||||
'data/sale_tour.xml',
|
||||
'data/ir_config_parameter.xml', # Needs mail_template_data
|
||||
|
||||
'wizard/account_accrued_orders_wizard_views.xml',
|
||||
'wizard/mass_cancel_orders_views.xml',
|
||||
'wizard/payment_link_wizard_views.xml',
|
||||
'wizard/res_config_settings_views.xml',
|
||||
'wizard/sale_make_invoice_advance_views.xml',
|
||||
'wizard/sale_order_cancel_views.xml',
|
||||
'wizard/sale_order_discount_views.xml',
|
||||
|
||||
# Define sale order views before their references
|
||||
'views/sale_order_views.xml',
|
||||
|
|
@ -42,14 +43,13 @@ This module contains all the common features of Sales Management and eCommerce.
|
|||
'views/account_views.xml',
|
||||
'views/crm_team_views.xml',
|
||||
'views/mail_activity_views.xml',
|
||||
'views/payment_templates.xml',
|
||||
'views/mail_activity_plan_views.xml',
|
||||
'views/payment_views.xml',
|
||||
'views/product_packaging_views.xml',
|
||||
'views/product_document_views.xml',
|
||||
'views/product_pricelist_item_views.xml',
|
||||
'views/product_template_views.xml',
|
||||
'views/product_views.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/res_partner_views.xml',
|
||||
'views/variant_templates.xml',
|
||||
'views/sale_onboarding_views.xml',
|
||||
'views/sale_order_line_views.xml',
|
||||
'views/sale_portal_templates.xml',
|
||||
'views/utm_campaign_views.xml',
|
||||
|
|
@ -64,30 +64,46 @@ This module contains all the common features of Sales Management and eCommerce.
|
|||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'sale/static/src/scss/sale_onboarding.scss',
|
||||
'sale/static/src/scss/product_configurator.scss',
|
||||
'sale/static/src/js/badge_extra_price/*',
|
||||
'sale/static/src/js/sale_action_helper/*',
|
||||
'sale/static/src/js/combo_configurator_dialog/*',
|
||||
'sale/static/src/js/models/*',
|
||||
'sale/static/src/js/product/*',
|
||||
'sale/static/src/js/product_card/*',
|
||||
'sale/static/src/js/product_configurator_dialog/*',
|
||||
'sale/static/src/js/product_list/*',
|
||||
'sale/static/src/js/product_template_attribute_line/*',
|
||||
'sale/static/src/js/quantity_buttons/*',
|
||||
'sale/static/src/js/sale_order_line_field/*',
|
||||
'sale/static/src/js/sale_progressbar_field.js',
|
||||
'sale/static/src/js/tours/sale.js',
|
||||
'sale/static/src/js/product_discount_field.js',
|
||||
'sale/static/src/js/upload_rfq_cog_menu/*',
|
||||
'sale/static/src/js/sale_product_field.js',
|
||||
'sale/static/src/js/sale_product_field.scss',
|
||||
'sale/static/src/js/sale_utils.js',
|
||||
'sale/static/src/xml/**/*',
|
||||
'sale/static/src/views/**/*',
|
||||
],
|
||||
'web.assets_frontend': [
|
||||
'sale/static/src/interactions/**/*',
|
||||
'sale/static/src/scss/sale_portal.scss',
|
||||
'sale/static/src/js/sale_portal_sidebar.js',
|
||||
'sale/static/src/js/sale_portal.js',
|
||||
'sale/static/src/js/payment_form.js',
|
||||
],
|
||||
'web.assets_tests': [
|
||||
'sale/static/tests/tours/**/*',
|
||||
'sale/static/src/js/tours/combo_configurator_tour_utils.js',
|
||||
'sale/static/src/js/tours/product_configurator_tour_utils.js',
|
||||
'sale/static/src/js/tours/tour_utils.js',
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
'sale/static/tests/**/*',
|
||||
('remove', 'sale/static/tests/tours/**/*')
|
||||
'web.assets_unit_tests': [
|
||||
'sale/static/tests/mock_server/**/*',
|
||||
'sale/static/tests/sale_test_helpers.js',
|
||||
'sale/static/tests/**/*.test.js',
|
||||
],
|
||||
'web.report_assets_common': [
|
||||
'sale/static/src/scss/sale_report.scss',
|
||||
],
|
||||
},
|
||||
'post_init_hook': '_synchronize_cron',
|
||||
'post_init_hook': '_post_init_hook',
|
||||
'author': 'Odoo S.A.',
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
|
|
|||
7
odoo-bringout-oca-ocb-sale/sale/const.py
Normal file
7
odoo-bringout-oca-ocb-sale/sale/const.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
# Mapping of config parameters to the crons they toggle.
|
||||
PARAM_CRON_MAPPING = {
|
||||
'sale.async_emails': 'sale.send_pending_emails_cron',
|
||||
'sale.automatic_invoice': 'sale.send_invoice_cron',
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import onboarding
|
||||
from . import combo_configurator
|
||||
from . import portal
|
||||
from . import variant
|
||||
from . import product_configurator
|
||||
|
|
|
|||
|
|
@ -0,0 +1,218 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from odoo.http import Controller, request, route
|
||||
from odoo.tools import groupby
|
||||
|
||||
|
||||
class SaleComboConfiguratorController(Controller):
|
||||
|
||||
@route(route='/sale/combo_configurator/get_data', type='jsonrpc', auth='user', readonly=True)
|
||||
def sale_combo_configurator_get_data(
|
||||
self,
|
||||
product_tmpl_id,
|
||||
quantity,
|
||||
date,
|
||||
currency_id=None,
|
||||
company_id=None,
|
||||
pricelist_id=None,
|
||||
selected_combo_items=None,
|
||||
**kwargs,
|
||||
):
|
||||
""" Return data about the specified combo product.
|
||||
|
||||
:param int product_tmpl_id: The product for which to get data, as a `product.template` id.
|
||||
:param int quantity: The quantity of the product.
|
||||
:param str date: The date to use to compute prices.
|
||||
:param int|None currency_id: The currency to use to compute prices, as a `res.currency` id.
|
||||
:param int|None company_id: The company to use, as a `res.company` id.
|
||||
:param int|None pricelist_id: The pricelist to use to compute prices, as a
|
||||
`product.pricelist` id.
|
||||
:param list(dict) selected_combo_items: The selected combo items, in the following format:
|
||||
{
|
||||
'id': int,
|
||||
'no_variant_ptav_ids': list(int),
|
||||
'custom_ptavs': list({
|
||||
'id': int,
|
||||
'value': str,
|
||||
}),
|
||||
}
|
||||
:param dict kwargs: Locally unused data passed to `_get_configurator_display_price` and
|
||||
`_get_additional_configurator_data`.
|
||||
:rtype: dict
|
||||
:return: A dict containing data about the combo product.
|
||||
"""
|
||||
if company_id:
|
||||
request.update_context(allowed_company_ids=[company_id])
|
||||
product_template = request.env['product.template'].browse(product_tmpl_id)
|
||||
currency = request.env['res.currency'].browse(currency_id)
|
||||
pricelist = request.env['product.pricelist'].browse(pricelist_id)
|
||||
date = datetime.fromisoformat(date)
|
||||
selected_combo_item_dict = {item['id']: item for item in selected_combo_items or []}
|
||||
|
||||
return {
|
||||
'product_tmpl_id': product_tmpl_id,
|
||||
'display_name': product_template.display_name,
|
||||
'quantity': quantity,
|
||||
'price': product_template._get_configurator_display_price(
|
||||
product_template, quantity, date, currency, pricelist, **kwargs
|
||||
)[0],
|
||||
'combos': [{
|
||||
'id': combo.id,
|
||||
'name': combo.name,
|
||||
'combo_items': [
|
||||
self._get_combo_item_data(
|
||||
combo,
|
||||
combo_item,
|
||||
selected_combo_item_dict.get(combo_item.id, {}),
|
||||
date,
|
||||
currency,
|
||||
pricelist,
|
||||
quantity=quantity,
|
||||
**kwargs,
|
||||
) for combo_item in combo.combo_item_ids if combo_item.product_id.active
|
||||
],
|
||||
} for combo in product_template.sudo().combo_ids],
|
||||
'currency_id': currency_id,
|
||||
**product_template._get_additional_configurator_data(
|
||||
product_template, date, currency, pricelist, quantity=quantity, **kwargs
|
||||
),
|
||||
}
|
||||
|
||||
@route(route='/sale/combo_configurator/get_price', type='jsonrpc', auth='user', readonly=True)
|
||||
def sale_combo_configurator_get_price(
|
||||
self,
|
||||
product_tmpl_id,
|
||||
quantity,
|
||||
date,
|
||||
currency_id=None,
|
||||
company_id=None,
|
||||
pricelist_id=None,
|
||||
**kwargs,
|
||||
):
|
||||
""" Return the price of the specified combo product.
|
||||
|
||||
:param int product_tmpl_id: The product for which to get the price, as a `product.template`
|
||||
id.
|
||||
:param int quantity: The quantity of the product.
|
||||
:param str date: The date to use to compute the price.
|
||||
:param int|None currency_id: The currency to use to compute the price, as a `res.currency`
|
||||
id.
|
||||
:param int|None company_id: The company to use, as a `res.company` id.
|
||||
:param int|None pricelist_id: The pricelist to use to compute the price, as a
|
||||
`product.pricelist` id.
|
||||
:param dict kwargs: Locally unused data passed to `_get_configurator_display_price`.
|
||||
:rtype: float
|
||||
:return: The price of the combo product.
|
||||
"""
|
||||
if company_id:
|
||||
request.update_context(allowed_company_ids=[company_id])
|
||||
product_template = request.env['product.template'].browse(product_tmpl_id)
|
||||
currency = request.env['res.currency'].browse(currency_id)
|
||||
pricelist = request.env['product.pricelist'].browse(pricelist_id)
|
||||
date = datetime.fromisoformat(date)
|
||||
|
||||
return product_template._get_configurator_display_price(
|
||||
product_template, quantity, date, currency, pricelist, **kwargs
|
||||
)[0]
|
||||
|
||||
def _get_combo_item_data(
|
||||
self, combo, combo_item, selected_combo_item, date, currency, pricelist, **kwargs
|
||||
):
|
||||
""" Return the price of the specified combo product.
|
||||
|
||||
:param product.combo combo: The combo for which to get the data.
|
||||
:param product.combo.item combo_item: The combo for which to get the data.
|
||||
:param datetime date: The date to use to compute prices.
|
||||
:param product.pricelist pricelist: The pricelist to use to compute prices.
|
||||
:param dict kwargs: Locally unused data passed to `_get_additional_configurator_data`.
|
||||
:rtype: dict
|
||||
:return: A dict containing data about the combo item.
|
||||
"""
|
||||
# A combo item is configurable if its product variant has:
|
||||
# - Configurable `no_variant` PTALs,
|
||||
# - Or custom PTAVs.
|
||||
is_configurable = any(
|
||||
ptal.attribute_id.create_variant == 'no_variant' and ptal._is_configurable()
|
||||
for ptal in combo_item.product_id.attribute_line_ids
|
||||
) or any(
|
||||
ptav.is_custom for ptav in combo_item.product_id.product_template_attribute_value_ids
|
||||
)
|
||||
# A combo item can be preselected if its combo choice has only one combo item, and that
|
||||
# combo item isn't configurable.
|
||||
is_preselected = len(combo.combo_item_ids) == 1 and not is_configurable
|
||||
|
||||
return {
|
||||
'id': combo_item.id,
|
||||
'extra_price': combo_item.extra_price,
|
||||
'is_preselected': is_preselected,
|
||||
'is_selected': bool(selected_combo_item) or is_preselected,
|
||||
'is_configurable': is_configurable,
|
||||
'product': {
|
||||
'id': combo_item.product_id.id,
|
||||
'product_tmpl_id': combo_item.product_id.product_tmpl_id.id,
|
||||
'display_name': combo_item.product_id.display_name,
|
||||
'ptals': self._get_ptals_data(combo_item.product_id, selected_combo_item),
|
||||
'description': combo_item.product_id.description_sale,
|
||||
**request.env['product.template']._get_additional_configurator_data(
|
||||
combo_item.product_id, date, currency, pricelist, **kwargs
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
def _get_ptals_data(self, product, selected_combo_item):
|
||||
""" Return data about the PTALs of the specified product.
|
||||
|
||||
:param product.product product: The product for which to get the PTALs.
|
||||
:param dict selected_combo_item: The selected combo item, in the following format:
|
||||
{
|
||||
'id': int,
|
||||
'no_variant_ptav_ids': list(int),
|
||||
'custom_ptavs': list({
|
||||
'id': int,
|
||||
'value': str,
|
||||
}),
|
||||
}
|
||||
:rtype: list(dict)
|
||||
:return: A list of dicts containing data about the specified product's PTALs.
|
||||
"""
|
||||
variant_ptavs = product.product_template_attribute_value_ids
|
||||
no_variant_ptavs = request.env['product.template.attribute.value'].browse(
|
||||
selected_combo_item.get('no_variant_ptav_ids')
|
||||
)
|
||||
preselected_ptavs = product.attribute_line_ids.filtered(
|
||||
lambda ptal: not ptal._is_configurable()
|
||||
).product_template_value_ids
|
||||
|
||||
ptavs_by_ptal_id = dict(groupby(
|
||||
variant_ptavs | no_variant_ptavs | preselected_ptavs,
|
||||
lambda ptav: ptav.attribute_line_id.id,
|
||||
))
|
||||
|
||||
custom_ptavs = selected_combo_item.get('custom_ptavs', [])
|
||||
custom_value_by_ptav_id = {ptav['id']: ptav['value'] for ptav in custom_ptavs}
|
||||
|
||||
return [{
|
||||
'id': ptal.id,
|
||||
'name': ptal.attribute_id.name,
|
||||
'create_variant': ptal.attribute_id.create_variant,
|
||||
'selected_ptavs': self._get_selected_ptavs_data(
|
||||
ptavs_by_ptal_id.get(ptal.id, []), custom_value_by_ptav_id
|
||||
),
|
||||
} for ptal in product.attribute_line_ids]
|
||||
|
||||
def _get_selected_ptavs_data(self, selected_ptavs, custom_value_by_ptav_id):
|
||||
""" Return data about the selected PTAVs of the specified product.
|
||||
|
||||
:param list(product.template.attribute.value) selected_ptavs: The selected PTAVs.
|
||||
:param dict custom_value_by_ptav_id: A mapping from PTAV ids to custom values.
|
||||
:rtype: list(dict)
|
||||
:return: A list of dicts containing data about the specified PTAL's selected PTAVs.
|
||||
"""
|
||||
return [{
|
||||
'id': ptav.id,
|
||||
'name': ptav.name,
|
||||
'price_extra': ptav.price_extra,
|
||||
'custom_value': custom_value_by_ptav_id.get(ptav.id),
|
||||
} for ptav in selected_ptavs]
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class OnboardingController(http.Controller):
|
||||
|
||||
@http.route('/sales/sale_quotation_onboarding_panel', auth='user', type='json')
|
||||
def sale_quotation_onboarding(self):
|
||||
""" Returns the `banner` for the sale onboarding panel.
|
||||
It can be empty if the user has closed it or if he doesn't have
|
||||
the permission to see it. """
|
||||
|
||||
company = request.env.company
|
||||
if not request.env.is_admin() or \
|
||||
company.sale_quotation_onboarding_state == 'closed':
|
||||
return {}
|
||||
|
||||
return {
|
||||
'html': request.env['ir.qweb']._render('sale.sale_quotation_onboarding_panel', {
|
||||
'company': company,
|
||||
'state': company.get_and_update_sale_quotation_onboarding_state()
|
||||
})
|
||||
}
|
||||
|
|
@ -1,21 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import binascii
|
||||
|
||||
from odoo import fields, http, SUPERUSER_ID, _
|
||||
from odoo import SUPERUSER_ID, _, fields, http
|
||||
from odoo.exceptions import AccessError, MissingError, ValidationError
|
||||
from odoo.fields import Command
|
||||
from odoo.http import request
|
||||
|
||||
from odoo.addons.payment.controllers import portal as payment_portal
|
||||
from odoo.addons.payment import utils as payment_utils
|
||||
from odoo.addons.portal.controllers.mail import _message_post_helper
|
||||
from odoo.addons.portal.controllers import portal
|
||||
from odoo.addons.payment.controllers import portal as payment_portal
|
||||
from odoo.addons.portal.controllers.portal import pager as portal_pager
|
||||
|
||||
|
||||
class CustomerPortal(portal.CustomerPortal):
|
||||
class CustomerPortal(payment_portal.PaymentPortal):
|
||||
|
||||
def _prepare_home_portal_values(self, counters):
|
||||
values = super()._prepare_home_portal_values(counters)
|
||||
|
|
@ -24,30 +21,28 @@ class CustomerPortal(portal.CustomerPortal):
|
|||
SaleOrder = request.env['sale.order']
|
||||
if 'quotation_count' in counters:
|
||||
values['quotation_count'] = SaleOrder.search_count(self._prepare_quotations_domain(partner)) \
|
||||
if SaleOrder.check_access_rights('read', raise_exception=False) else 0
|
||||
if SaleOrder.has_access('read') else 0
|
||||
if 'order_count' in counters:
|
||||
values['order_count'] = SaleOrder.search_count(self._prepare_orders_domain(partner)) \
|
||||
if SaleOrder.check_access_rights('read', raise_exception=False) else 0
|
||||
values['order_count'] = SaleOrder.search_count(self._prepare_orders_domain(partner), limit=1) \
|
||||
if SaleOrder.has_access('read') else 0
|
||||
|
||||
return values
|
||||
|
||||
def _prepare_quotations_domain(self, partner):
|
||||
return [
|
||||
('message_partner_ids', 'child_of', [partner.commercial_partner_id.id]),
|
||||
('state', 'in', ['sent', 'cancel'])
|
||||
('partner_id', 'child_of', [partner.commercial_partner_id.id]),
|
||||
('state', '=', 'sent')
|
||||
]
|
||||
|
||||
def _prepare_orders_domain(self, partner):
|
||||
return [
|
||||
('message_partner_ids', 'child_of', [partner.commercial_partner_id.id]),
|
||||
('state', 'in', ['sale', 'done'])
|
||||
('partner_id', 'child_of', [partner.commercial_partner_id.id]),
|
||||
('state', '=', 'sale'),
|
||||
]
|
||||
|
||||
def _get_sale_searchbar_sortings(self):
|
||||
return {
|
||||
'date': {'label': _('Order Date'), 'order': 'date_order desc'},
|
||||
'name': {'label': _('Reference'), 'order': 'name'},
|
||||
'stage': {'label': _('Stage'), 'order': 'state'},
|
||||
}
|
||||
|
||||
def _prepare_sale_portal_rendering_values(
|
||||
|
|
@ -75,14 +70,19 @@ class CustomerPortal(portal.CustomerPortal):
|
|||
if date_begin and date_end:
|
||||
domain += [('create_date', '>', date_begin), ('create_date', '<=', date_end)]
|
||||
|
||||
url_args = {'date_begin': date_begin, 'date_end': date_end}
|
||||
|
||||
if len(searchbar_sortings) > 1:
|
||||
url_args['sortby'] = sortby
|
||||
|
||||
pager_values = portal_pager(
|
||||
url=url,
|
||||
total=SaleOrder.search_count(domain),
|
||||
total=SaleOrder.search_count(domain) if SaleOrder.has_access('read') else 0,
|
||||
page=page,
|
||||
step=self._items_per_page,
|
||||
url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby},
|
||||
url_args=url_args,
|
||||
)
|
||||
orders = SaleOrder.search(domain, order=sort_order, limit=self._items_per_page, offset=pager_values['offset'])
|
||||
orders = SaleOrder.search(domain, order=sort_order, limit=self._items_per_page, offset=pager_values['offset']) if SaleOrder.has_access('read') else SaleOrder
|
||||
|
||||
values.update({
|
||||
'date': date_begin,
|
||||
|
|
@ -91,12 +91,18 @@ class CustomerPortal(portal.CustomerPortal):
|
|||
'page_name': 'quote' if quotation_page else 'order',
|
||||
'pager': pager_values,
|
||||
'default_url': url,
|
||||
'searchbar_sortings': searchbar_sortings,
|
||||
'sortby': sortby,
|
||||
})
|
||||
|
||||
if len(searchbar_sortings) > 1:
|
||||
values.update({
|
||||
'sortby': sortby,
|
||||
'searchbar_sortings': searchbar_sortings,
|
||||
})
|
||||
|
||||
return values
|
||||
|
||||
# Two following routes cannot be readonly because of the call to `_portal_ensure_token` on all
|
||||
# displayed orders, to assign an access token (triggering a sql update on flush)
|
||||
@http.route(['/my/quotes', '/my/quotes/page/<int:page>'], type='http', auth="user", website=True)
|
||||
def portal_my_quotes(self, **kwargs):
|
||||
values = self._prepare_sale_portal_rendering_values(quotation_page=True, **kwargs)
|
||||
|
|
@ -110,16 +116,38 @@ class CustomerPortal(portal.CustomerPortal):
|
|||
return request.render("sale.portal_my_orders", values)
|
||||
|
||||
@http.route(['/my/orders/<int:order_id>'], type='http', auth="public", website=True)
|
||||
def portal_order_page(self, order_id, report_type=None, access_token=None, message=False, download=False, **kw):
|
||||
def portal_order_page(
|
||||
self,
|
||||
order_id,
|
||||
report_type=None,
|
||||
access_token=None,
|
||||
message=False,
|
||||
download=False,
|
||||
payment_amount=None,
|
||||
amount_selection=None,
|
||||
**kw
|
||||
):
|
||||
try:
|
||||
order_sudo = self._document_check_access('sale.order', order_id, access_token=access_token)
|
||||
except (AccessError, MissingError):
|
||||
return request.redirect('/my')
|
||||
|
||||
if report_type in ('html', 'pdf', 'text'):
|
||||
return self._show_report(model=order_sudo, report_type=report_type, report_ref='sale.action_report_saleorder', download=download)
|
||||
payment_amount = self._cast_as_float(payment_amount)
|
||||
prepayment_amount = order_sudo._get_prepayment_required_amount()
|
||||
if payment_amount and payment_amount < prepayment_amount and order_sudo.state != 'sale':
|
||||
raise MissingError(_("The amount is lower than the prepayment amount."))
|
||||
|
||||
if request.env.user.share and access_token:
|
||||
if report_type in ('html', 'pdf', 'text'):
|
||||
return self._show_report(
|
||||
model=order_sudo,
|
||||
report_type=report_type,
|
||||
report_ref='sale.action_report_saleorder',
|
||||
download=download,
|
||||
)
|
||||
|
||||
# If the route is fetched from the link previewer avoid triggering that quotation is viewed.
|
||||
is_link_preview = request.httprequest.headers.get('Odoo-Link-Preview')
|
||||
if request.env.user.share and access_token and is_link_preview != 'True':
|
||||
# If a public/portal user accesses the order with the access token
|
||||
# Log a note on the chatter.
|
||||
today = fields.Date.today().isoformat()
|
||||
|
|
@ -130,33 +158,37 @@ class CustomerPortal(portal.CustomerPortal):
|
|||
# The "Quotation viewed by customer" log note is an information
|
||||
# dedicated to the salesman and shouldn't be translated in the customer/website lgg
|
||||
context = {'lang': order_sudo.user_id.partner_id.lang or order_sudo.company_id.partner_id.lang}
|
||||
msg = _('Quotation viewed by customer %s', order_sudo.partner_id.name if request.env.user._is_public() else request.env.user.partner_id.name)
|
||||
author = order_sudo.partner_id if request.env.user._is_public() else request.env.user.partner_id
|
||||
msg = _('Quotation viewed by customer %s', author.name)
|
||||
del context
|
||||
_message_post_helper(
|
||||
"sale.order",
|
||||
order_sudo.id,
|
||||
message=msg,
|
||||
token=order_sudo.access_token,
|
||||
order_sudo.with_user(SUPERUSER_ID).message_post(
|
||||
body=msg,
|
||||
message_type="notification",
|
||||
subtype_xmlid="mail.mt_note",
|
||||
partner_ids=order_sudo.user_id.sudo().partner_id.ids,
|
||||
subtype_xmlid="sale.mt_order_viewed",
|
||||
)
|
||||
|
||||
backend_url = f'/web#model={order_sudo._name}'\
|
||||
f'&id={order_sudo.id}'\
|
||||
f'&action={order_sudo._get_portal_return_action().id}'\
|
||||
f'&view_type=form'
|
||||
backend_url = f'/odoo/action-{order_sudo._get_portal_return_action().id}/{order_sudo.id}'
|
||||
values = {
|
||||
'sale_order': order_sudo,
|
||||
'product_documents': order_sudo._get_product_documents(),
|
||||
'message': message,
|
||||
'report_type': 'html',
|
||||
'backend_url': backend_url,
|
||||
'res_company': order_sudo.company_id, # Used to display correct company logo
|
||||
'payment_amount': payment_amount,
|
||||
}
|
||||
|
||||
# Payment values
|
||||
if order_sudo._has_to_be_paid():
|
||||
values.update(self._get_payment_values(order_sudo))
|
||||
if order_sudo._has_to_be_paid() or (payment_amount and not order_sudo.is_expired):
|
||||
values.update(self._get_payment_values(
|
||||
order_sudo,
|
||||
is_down_payment=self._determine_is_down_payment(
|
||||
order_sudo, amount_selection, payment_amount
|
||||
),
|
||||
payment_amount=payment_amount,
|
||||
))
|
||||
else:
|
||||
values['payment_amount'] = None
|
||||
|
||||
if order_sudo.state in ('draft', 'sent', 'cancel'):
|
||||
history_session_key = 'my_quotations_history'
|
||||
|
|
@ -164,58 +196,114 @@ class CustomerPortal(portal.CustomerPortal):
|
|||
history_session_key = 'my_orders_history'
|
||||
|
||||
values = self._get_page_view_values(
|
||||
order_sudo, access_token, values, history_session_key, False)
|
||||
order_sudo, access_token, values, history_session_key, False, **kw)
|
||||
|
||||
return request.render('sale.sale_order_portal_template', values)
|
||||
|
||||
def _get_payment_values(self, order_sudo):
|
||||
def _determine_is_down_payment(self, order_sudo, amount_selection, payment_amount):
|
||||
""" Determine whether the current payment is a down payment.
|
||||
|
||||
:param sale.order order_sudo: The sales order being paid.
|
||||
:param str amount_selection: The amount selection specified in the payment link.
|
||||
:param float payment_amount: The amount suggested in the payment link.
|
||||
:return: Whether the current payment is a down payment.
|
||||
:rtype: bool
|
||||
"""
|
||||
if amount_selection == 'down_payment': # The customer chose to pay a down payment.
|
||||
is_down_payment = True
|
||||
elif amount_selection == 'full_amount': # The customer chose to pay the full amount.
|
||||
is_down_payment = False
|
||||
else: # No choice has been specified yet.
|
||||
is_down_payment = (
|
||||
order_sudo.prepayment_percent < 1.0 if payment_amount is None
|
||||
else payment_amount < order_sudo.amount_total
|
||||
)
|
||||
return is_down_payment
|
||||
|
||||
def _get_payment_values(self, order_sudo, is_down_payment=False, payment_amount=None, **kwargs):
|
||||
""" Return the payment-specific QWeb context values.
|
||||
|
||||
:param recordset order_sudo: The sales order being paid, as a `sale.order` record.
|
||||
:param sale.order order_sudo: The sales order being paid.
|
||||
:param bool is_down_payment: Whether the current payment is a down payment.
|
||||
:param float payment_amount: The amount suggested in the payment link.
|
||||
:param dict kwargs: Locally unused data passed to `_get_compatible_providers` and
|
||||
`_get_available_tokens`.
|
||||
:return: The payment-specific values.
|
||||
:rtype: dict
|
||||
"""
|
||||
company = order_sudo.company_id
|
||||
logged_in = not request.env.user._is_public()
|
||||
partner_sudo = request.env.user.partner_id if logged_in else order_sudo.partner_id
|
||||
currency = order_sudo.currency_id
|
||||
|
||||
if is_down_payment:
|
||||
if payment_amount and payment_amount < order_sudo.amount_total:
|
||||
amount = payment_amount
|
||||
else:
|
||||
amount = order_sudo._get_prepayment_required_amount()
|
||||
elif order_sudo.state == 'sale':
|
||||
amount = payment_amount or order_sudo.amount_total
|
||||
else:
|
||||
amount = order_sudo.amount_total
|
||||
|
||||
availability_report = {}
|
||||
# Select all the payment methods and tokens that match the payment context.
|
||||
providers_sudo = request.env['payment.provider'].sudo()._get_compatible_providers(
|
||||
order_sudo.company_id.id,
|
||||
order_sudo.partner_id.id,
|
||||
order_sudo.amount_total,
|
||||
currency_id=order_sudo.currency_id.id,
|
||||
company.id,
|
||||
partner_sudo.id,
|
||||
amount,
|
||||
currency_id=currency.id,
|
||||
sale_order_id=order_sudo.id,
|
||||
) # In sudo mode to read the fields of providers and partner (if not logged in)
|
||||
tokens = request.env['payment.token'].search([
|
||||
('provider_id', 'in', providers_sudo.ids),
|
||||
('partner_id', '=', order_sudo.partner_id.id)
|
||||
]) if logged_in else request.env['payment.token']
|
||||
# Make sure that the partner's company matches the order's company.
|
||||
if not payment_portal.PaymentPortal._can_partner_pay_in_company(
|
||||
order_sudo.partner_id, order_sudo.company_id
|
||||
):
|
||||
providers_sudo = request.env['payment.provider'].sudo()
|
||||
tokens = request.env['payment.token']
|
||||
fees_by_provider = {
|
||||
provider: provider._compute_fees(
|
||||
order_sudo.amount_total,
|
||||
order_sudo.currency_id,
|
||||
order_sudo.partner_id.country_id,
|
||||
) for provider in providers_sudo.filtered('fees_active')
|
||||
report=availability_report,
|
||||
**kwargs,
|
||||
) # In sudo mode to read the fields of providers and partner (if logged out).
|
||||
payment_methods_sudo = request.env['payment.method'].sudo()._get_compatible_payment_methods(
|
||||
providers_sudo.ids,
|
||||
partner_sudo.id,
|
||||
currency_id=currency.id,
|
||||
sale_order_id=order_sudo.id,
|
||||
report=availability_report,
|
||||
**kwargs,
|
||||
) # In sudo mode to read the fields of providers.
|
||||
tokens_sudo = request.env['payment.token'].sudo()._get_available_tokens(
|
||||
providers_sudo.ids, partner_sudo.id, **kwargs
|
||||
) # In sudo mode to read the partner's tokens (if logged out) and provider fields.
|
||||
|
||||
# Make sure that the partner's company matches the invoice's company.
|
||||
company_mismatch = not payment_portal.PaymentPortal._can_partner_pay_in_company(
|
||||
partner_sudo, company
|
||||
)
|
||||
|
||||
portal_page_values = {
|
||||
'company_mismatch': company_mismatch,
|
||||
'expected_company': company,
|
||||
'payment_amount': payment_amount,
|
||||
}
|
||||
return {
|
||||
'providers': providers_sudo,
|
||||
'tokens': tokens,
|
||||
'fees_by_provider': fees_by_provider,
|
||||
'show_tokenize_input': PaymentPortal._compute_show_tokenize_input_mapping(
|
||||
providers_sudo, logged_in=logged_in, sale_order_id=order_sudo.id
|
||||
payment_form_values = {
|
||||
'show_tokenize_input_mapping': PaymentPortal._compute_show_tokenize_input_mapping(
|
||||
providers_sudo, sale_order_id=order_sudo.id
|
||||
),
|
||||
'amount': order_sudo.amount_total,
|
||||
'currency': order_sudo.pricelist_id.currency_id,
|
||||
'partner_id': order_sudo.partner_id.id,
|
||||
'access_token': order_sudo.access_token,
|
||||
}
|
||||
payment_context = {
|
||||
'amount': amount,
|
||||
'currency': currency,
|
||||
'partner_id': partner_sudo.id,
|
||||
'providers_sudo': providers_sudo,
|
||||
'payment_methods_sudo': payment_methods_sudo,
|
||||
'tokens_sudo': tokens_sudo,
|
||||
'availability_report': availability_report,
|
||||
'transaction_route': order_sudo.get_portal_url(suffix='/transaction'),
|
||||
'landing_route': order_sudo.get_portal_url(),
|
||||
'access_token': order_sudo._portal_ensure_token(),
|
||||
}
|
||||
return {
|
||||
**portal_page_values,
|
||||
**payment_form_values,
|
||||
**payment_context,
|
||||
**self._get_extra_payment_form_values(**kwargs),
|
||||
}
|
||||
|
||||
@http.route(['/my/orders/<int:order_id>/accept'], type='json', auth="public", website=True)
|
||||
@http.route(['/my/orders/<int:order_id>/accept'], type='jsonrpc', auth="public", website=True)
|
||||
def portal_quote_accept(self, order_id, access_token=None, name=None, signature=None):
|
||||
# get from query string if not on json param
|
||||
access_token = access_token or request.httprequest.args.get('access_token')
|
||||
|
|
@ -235,27 +323,31 @@ class CustomerPortal(portal.CustomerPortal):
|
|||
'signed_on': fields.Datetime.now(),
|
||||
'signature': signature,
|
||||
})
|
||||
request.env.cr.commit()
|
||||
# flush now to make signature data available to PDF render request
|
||||
request.env.cr.flush()
|
||||
except (TypeError, binascii.Error) as e:
|
||||
return {'error': _('Invalid signature data.')}
|
||||
|
||||
if not order_sudo._has_to_be_paid():
|
||||
order_sudo.action_confirm()
|
||||
order_sudo._send_order_confirmation_mail()
|
||||
order_sudo._validate_order()
|
||||
|
||||
pdf = request.env['ir.actions.report'].sudo()._render_qweb_pdf('sale.action_report_saleorder', [order_sudo.id])[0]
|
||||
|
||||
_message_post_helper(
|
||||
'sale.order',
|
||||
order_sudo.id,
|
||||
_('Order signed by %s', name),
|
||||
order_sudo.message_post(
|
||||
attachments=[('%s.pdf' % order_sudo.name, pdf)],
|
||||
token=access_token,
|
||||
author_id=(
|
||||
order_sudo.partner_id.id
|
||||
if request.env.user._is_public()
|
||||
else request.env.user.partner_id.id
|
||||
),
|
||||
body=_('Order signed by %s', name),
|
||||
message_type='comment',
|
||||
subtype_xmlid='mail.mt_comment',
|
||||
)
|
||||
|
||||
query_string = '&message=sign_ok'
|
||||
if order_sudo._has_to_be_paid(True):
|
||||
query_string += '#allow_payment=yes'
|
||||
if order_sudo._has_to_be_paid():
|
||||
query_string += '&allow_payment=yes'
|
||||
return {
|
||||
'force_refresh': True,
|
||||
'redirect_url': order_sudo.get_portal_url(query_string=query_string),
|
||||
|
|
@ -270,11 +362,22 @@ class CustomerPortal(portal.CustomerPortal):
|
|||
|
||||
if order_sudo._has_to_be_signed() and decline_message:
|
||||
order_sudo._action_cancel()
|
||||
_message_post_helper(
|
||||
'sale.order',
|
||||
order_sudo.id,
|
||||
decline_message,
|
||||
token=access_token,
|
||||
# The currency is manually cached while in a sudoed environment to prevent an
|
||||
# AccessError. The state of the Sales Order is a dependency of
|
||||
# `untaxed_amount_to_invoice`, which is a monetary field. They require the currency to
|
||||
# ensure the values are saved in the correct format. However, the currency cannot be
|
||||
# read directly during the flush due to access rights, necessitating manual caching.
|
||||
order_sudo.order_line.currency_id
|
||||
|
||||
order_sudo.message_post(
|
||||
author_id=(
|
||||
order_sudo.partner_id.id
|
||||
if request.env.user._is_public()
|
||||
else request.env.user.partner_id.id
|
||||
),
|
||||
body=decline_message,
|
||||
message_type='comment',
|
||||
subtype_xmlid='mail.mt_comment',
|
||||
)
|
||||
redirect_url = order_sudo.get_portal_url()
|
||||
else:
|
||||
|
|
@ -282,10 +385,55 @@ class CustomerPortal(portal.CustomerPortal):
|
|||
|
||||
return request.redirect(redirect_url)
|
||||
|
||||
@http.route('/my/orders/<int:order_id>/document/<int:document_id>', type='http', auth='public', readonly=True)
|
||||
def portal_quote_document(self, order_id, document_id, access_token):
|
||||
try:
|
||||
order_sudo = self._document_check_access('sale.order', order_id, access_token=access_token)
|
||||
except (AccessError, MissingError):
|
||||
return request.redirect('/my')
|
||||
|
||||
document = request.env['product.document'].browse(document_id).sudo().exists()
|
||||
if not document or not document.active:
|
||||
return request.redirect('/my')
|
||||
|
||||
if document not in order_sudo._get_product_documents():
|
||||
return request.redirect('/my')
|
||||
|
||||
return request.env['ir.binary']._get_stream_from(
|
||||
document.ir_attachment_id,
|
||||
).get_response(as_attachment=True)
|
||||
|
||||
@http.route(['/my/orders/<int:order_id>/download_edi'], auth="public", website=True)
|
||||
def portal_my_sale_order_download_edi(self, order_id=None, access_token=None, **kw):
|
||||
""" An endpoint to download EDI file representation."""
|
||||
try:
|
||||
order_sudo = self._document_check_access('sale.order', order_id, access_token=access_token)
|
||||
except (AccessError, MissingError):
|
||||
return request.redirect('/my')
|
||||
|
||||
builders = order_sudo._get_edi_builders()
|
||||
|
||||
# This handles only one builder for now, more can be added in the future
|
||||
# TODO: add builder choice on modal
|
||||
if len(builders) == 0:
|
||||
return request.redirect('/my')
|
||||
builder = builders[0]
|
||||
|
||||
xml_content = builder._export_order(order_sudo)
|
||||
|
||||
download_name = builder._export_invoice_filename(order_sudo) # works even if it's a SO or PO
|
||||
|
||||
http_headers = [
|
||||
('Content-Type', 'text/xml'),
|
||||
('Content-Length', len(xml_content)),
|
||||
('Content-Disposition', f'attachment; filename={download_name}')
|
||||
]
|
||||
return request.make_response(xml_content, headers=http_headers)
|
||||
|
||||
|
||||
class PaymentPortal(payment_portal.PaymentPortal):
|
||||
|
||||
@http.route('/my/orders/<int:order_id>/transaction', type='json', auth='public')
|
||||
@http.route('/my/orders/<int:order_id>/transaction', type='jsonrpc', auth='public')
|
||||
def portal_order_transaction(self, order_id, access_token, **kwargs):
|
||||
""" Create a draft transaction and return its processing values.
|
||||
|
||||
|
|
@ -304,92 +452,16 @@ class PaymentPortal(payment_portal.PaymentPortal):
|
|||
except AccessError:
|
||||
raise ValidationError(_("The access token is invalid."))
|
||||
|
||||
logged_in = not request.env.user._is_public()
|
||||
partner_sudo = request.env.user.partner_id if logged_in else order_sudo.partner_invoice_id
|
||||
self._validate_transaction_kwargs(kwargs)
|
||||
kwargs.update({
|
||||
'reference_prefix': None, # Allow the reference to be computed based on the order
|
||||
'partner_id': order_sudo.partner_invoice_id.id,
|
||||
'partner_id': partner_sudo.id,
|
||||
'currency_id': order_sudo.currency_id.id,
|
||||
'sale_order_id': order_id, # Include the SO to allow Subscriptions tokenizing the tx
|
||||
})
|
||||
kwargs.pop('custom_create_values', None) # Don't allow passing arbitrary create values
|
||||
tx_sudo = self._create_transaction(
|
||||
custom_create_values={'sale_order_ids': [Command.set([order_id])]}, **kwargs,
|
||||
)
|
||||
|
||||
return tx_sudo._get_processing_values()
|
||||
|
||||
# Payment overrides
|
||||
|
||||
@http.route()
|
||||
def payment_pay(self, *args, amount=None, sale_order_id=None, access_token=None, **kwargs):
|
||||
""" Override of payment to replace the missing transaction values by that of the sale order.
|
||||
|
||||
This is necessary for the reconciliation as all transaction values, excepted the amount,
|
||||
need to match exactly that of the sale order.
|
||||
|
||||
:param str amount: The (possibly partial) amount to pay used to check the access token
|
||||
:param str sale_order_id: The sale order for which a payment id made, as a `sale.order` id
|
||||
:param str access_token: The access token used to authenticate the partner
|
||||
:return: The result of the parent method
|
||||
:rtype: str
|
||||
:raise: ValidationError if the order id is invalid
|
||||
"""
|
||||
# Cast numeric parameters as int or float and void them if their str value is malformed
|
||||
amount = self._cast_as_float(amount)
|
||||
sale_order_id = self._cast_as_int(sale_order_id)
|
||||
if sale_order_id:
|
||||
order_sudo = request.env['sale.order'].sudo().browse(sale_order_id).exists()
|
||||
if not order_sudo:
|
||||
raise ValidationError(_("The provided parameters are invalid."))
|
||||
|
||||
# Check the access token against the order values. Done after fetching the order as we
|
||||
# need the order fields to check the access token.
|
||||
if not payment_utils.check_access_token(
|
||||
access_token, order_sudo.partner_invoice_id.id, amount, order_sudo.currency_id.id
|
||||
):
|
||||
raise ValidationError(_("The provided parameters are invalid."))
|
||||
|
||||
kwargs.update({
|
||||
'currency_id': order_sudo.currency_id.id,
|
||||
'partner_id': order_sudo.partner_invoice_id.id,
|
||||
'company_id': order_sudo.company_id.id,
|
||||
'sale_order_id': sale_order_id,
|
||||
})
|
||||
return super().payment_pay(*args, amount=amount, access_token=access_token, **kwargs)
|
||||
|
||||
def _get_custom_rendering_context_values(self, sale_order_id=None, **kwargs):
|
||||
""" Override of payment to add the sale order id in the custom rendering context values.
|
||||
|
||||
:param int sale_order_id: The sale order for which a payment id made, as a `sale.order` id
|
||||
:return: The extended rendering context values
|
||||
:rtype: dict
|
||||
"""
|
||||
rendering_context_values = super()._get_custom_rendering_context_values(
|
||||
sale_order_id=sale_order_id, **kwargs
|
||||
)
|
||||
if sale_order_id:
|
||||
rendering_context_values['sale_order_id'] = sale_order_id
|
||||
|
||||
# Interrupt the payment flow if the sales order has been canceled.
|
||||
order_sudo = request.env['sale.order'].sudo().browse(sale_order_id)
|
||||
if order_sudo.state == 'cancel':
|
||||
rendering_context_values['amount'] = 0.0
|
||||
return rendering_context_values
|
||||
|
||||
def _create_transaction(self, *args, sale_order_id=None, custom_create_values=None, **kwargs):
|
||||
""" Override of payment to add the sale order id in the custom create values.
|
||||
|
||||
:param int sale_order_id: The sale order for which a payment id made, as a `sale.order` id
|
||||
:param dict custom_create_values: Additional create values overwriting the default ones
|
||||
:return: The result of the parent method
|
||||
:rtype: recordset of `payment.transaction`
|
||||
"""
|
||||
if sale_order_id:
|
||||
if custom_create_values is None:
|
||||
custom_create_values = {}
|
||||
# As this override is also called if the flow is initiated from sale or website_sale, we
|
||||
# need not to override whatever value these modules could have already set
|
||||
if 'sale_order_ids' not in custom_create_values: # We are in the payment module's flow
|
||||
custom_create_values['sale_order_ids'] = [Command.set([int(sale_order_id)])]
|
||||
|
||||
return super()._create_transaction(
|
||||
*args, sale_order_id=sale_order_id, custom_create_values=custom_create_values, **kwargs
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,437 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from odoo.http import Controller, request, route
|
||||
|
||||
|
||||
class SaleProductConfiguratorController(Controller):
|
||||
|
||||
@route(route='/sale/product_configurator/get_values', type='jsonrpc', auth='user', readonly=True)
|
||||
def sale_product_configurator_get_values(
|
||||
self,
|
||||
product_template_id,
|
||||
quantity,
|
||||
currency_id,
|
||||
so_date,
|
||||
product_uom_id=None,
|
||||
company_id=None,
|
||||
pricelist_id=None,
|
||||
ptav_ids=None,
|
||||
only_main_product=False,
|
||||
**kwargs,
|
||||
):
|
||||
"""Return all product information needed for the product configurator.
|
||||
|
||||
:param int product_template_id: The product for which to seek information, as a
|
||||
`product.template` id.
|
||||
:param int quantity: The quantity of the product.
|
||||
:param int currency_id: The currency of the transaction, as a `res.currency` id.
|
||||
:param str so_date: The date of the `sale.order`, to compute the price at the right rate.
|
||||
:param int|None product_uom_id: The unit of measure of the product, as a `uom.uom` id.
|
||||
:param int|None company_id: The company to use, as a `res.company` id.
|
||||
:param int|None pricelist_id: The pricelist to use, as a `product.pricelist` id.
|
||||
:param list(int)|None ptav_ids: The combination of the product, as a list of
|
||||
`product.template.attribute.value` ids.
|
||||
:param bool only_main_product: Whether the optional products should be included or not.
|
||||
:param dict kwargs: Locally unused data passed to `_get_product_information`.
|
||||
:rtype: dict
|
||||
:return: A dict containing a list of products and a list of optional products information,
|
||||
generated by :meth:`_get_product_information`.
|
||||
"""
|
||||
if company_id:
|
||||
request.update_context(allowed_company_ids=[company_id])
|
||||
product_template = self._get_product_template(product_template_id)
|
||||
|
||||
combination = request.env['product.template.attribute.value']
|
||||
if ptav_ids:
|
||||
combination = request.env['product.template.attribute.value'].browse(ptav_ids).filtered(
|
||||
lambda ptav: ptav.product_tmpl_id.id == product_template_id
|
||||
)
|
||||
# Set missing attributes (unsaved no_variant attributes, or new attribute on existing product)
|
||||
unconfigured_ptals = (
|
||||
product_template.attribute_line_ids - combination.attribute_line_id).filtered(
|
||||
lambda ptal: ptal.attribute_id.display_type != 'multi')
|
||||
combination += unconfigured_ptals.mapped(
|
||||
lambda ptal: ptal.product_template_value_ids._only_active()[:1]
|
||||
)
|
||||
if not combination:
|
||||
combination = product_template._get_first_possible_combination()
|
||||
currency = request.env['res.currency'].browse(currency_id)
|
||||
pricelist = request.env['product.pricelist'].browse(pricelist_id)
|
||||
so_date = datetime.fromisoformat(so_date)
|
||||
|
||||
return {
|
||||
'products': [
|
||||
dict(
|
||||
**self._get_product_information(
|
||||
product_template,
|
||||
combination,
|
||||
currency,
|
||||
pricelist,
|
||||
so_date,
|
||||
quantity=quantity,
|
||||
product_uom_id=product_uom_id,
|
||||
**kwargs,
|
||||
),
|
||||
)
|
||||
],
|
||||
'optional_products': [
|
||||
dict(
|
||||
**self._get_product_information(
|
||||
optional_product_template,
|
||||
optional_product_template._get_first_possible_combination(
|
||||
parent_combination=combination
|
||||
),
|
||||
currency,
|
||||
pricelist,
|
||||
so_date,
|
||||
# giving all the ptav of the parent product to get all the exclusions
|
||||
parent_combination=product_template.attribute_line_ids.\
|
||||
product_template_value_ids,
|
||||
**kwargs,
|
||||
),
|
||||
parent_product_tmpl_id=product_template.id,
|
||||
) for optional_product_template in product_template.optional_product_ids if
|
||||
self._should_show_product(optional_product_template, combination)
|
||||
] if not only_main_product else [],
|
||||
'currency_id': currency_id,
|
||||
}
|
||||
|
||||
@route(
|
||||
route='/sale/product_configurator/create_product',
|
||||
type='jsonrpc',
|
||||
auth='user',
|
||||
methods=['POST'],
|
||||
)
|
||||
def sale_product_configurator_create_product(self, product_template_id, ptav_ids):
|
||||
"""Create the product when there is a dynamic attribute in the combination.
|
||||
|
||||
:param int product_template_id: The product for which to seek information, as a
|
||||
`product.template` id.
|
||||
:param list(int) ptav_ids: The combination of the product, as a list of
|
||||
`product.template.attribute.value` ids.
|
||||
:rtype: int
|
||||
:return: The product created, as a `product.product` id.
|
||||
"""
|
||||
product_template = self._get_product_template(product_template_id)
|
||||
combination = request.env['product.template.attribute.value'].browse(ptav_ids)
|
||||
product = product_template._create_product_variant(combination)
|
||||
return product.id
|
||||
|
||||
@route(
|
||||
route='/sale/product_configurator/update_combination',
|
||||
type='jsonrpc',
|
||||
auth='user',
|
||||
methods=['POST'],
|
||||
readonly=True,
|
||||
)
|
||||
def sale_product_configurator_update_combination(
|
||||
self,
|
||||
product_template_id,
|
||||
ptav_ids,
|
||||
currency_id,
|
||||
so_date,
|
||||
quantity,
|
||||
product_uom_id=None,
|
||||
company_id=None,
|
||||
pricelist_id=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Return the updated combination information.
|
||||
|
||||
:param int product_template_id: The product for which to seek information, as a
|
||||
`product.template` id.
|
||||
:param list(int) ptav_ids: The combination of the product, as a list of
|
||||
`product.template.attribute.value` ids.
|
||||
:param int currency_id: The currency of the transaction, as a `res.currency` id.
|
||||
:param str so_date: The date of the `sale.order`, to compute the price at the right rate.
|
||||
:param int quantity: The quantity of the product.
|
||||
:param int|None product_uom_id: The unit of measure of the product, as a `uom.uom` id.
|
||||
:param int|None company_id: The company to use, as a `res.company` id.
|
||||
:param int|None pricelist_id: The pricelist to use, as a `product.pricelist` id.
|
||||
:param dict kwargs: Locally unused data passed to `_get_basic_product_information`.
|
||||
:rtype: dict
|
||||
:return: Basic informations about a product, generated by
|
||||
:meth:`_get_basic_product_information`.
|
||||
"""
|
||||
if company_id:
|
||||
request.update_context(allowed_company_ids=[company_id])
|
||||
product_template = self._get_product_template(product_template_id)
|
||||
pricelist = request.env['product.pricelist'].browse(pricelist_id)
|
||||
product_uom = request.env['uom.uom'].browse(product_uom_id)
|
||||
currency = request.env['res.currency'].browse(currency_id)
|
||||
combination = request.env['product.template.attribute.value'].browse(ptav_ids)
|
||||
product = product_template._get_variant_for_combination(combination)
|
||||
|
||||
values = self._get_basic_product_information(
|
||||
product or product_template,
|
||||
pricelist,
|
||||
combination,
|
||||
quantity=quantity or 0.0,
|
||||
uom=product_uom,
|
||||
currency=currency,
|
||||
date=datetime.fromisoformat(so_date),
|
||||
**kwargs,
|
||||
)
|
||||
# Shouldn't be sent client-side
|
||||
values.pop('pricelist_rule_id', None)
|
||||
return values
|
||||
|
||||
@route(
|
||||
route='/sale/product_configurator/get_optional_products',
|
||||
type='jsonrpc',
|
||||
auth='user',
|
||||
readonly=True,
|
||||
)
|
||||
def sale_product_configurator_get_optional_products(
|
||||
self,
|
||||
product_template_id,
|
||||
ptav_ids,
|
||||
parent_ptav_ids,
|
||||
currency_id,
|
||||
so_date,
|
||||
company_id=None,
|
||||
pricelist_id=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Return information about optional products for the given `product.template`.
|
||||
|
||||
:param int product_template_id: The product for which to seek information, as a
|
||||
`product.template` id.
|
||||
:param list(int) ptav_ids: The combination of the product, as a list of
|
||||
`product.template.attribute.value` ids.
|
||||
:param list(int) parent_ptav_ids: The combination of the parent product, as a list of
|
||||
`product.template.attribute.value` ids.
|
||||
:param int currency_id: The currency of the transaction, as a `res.currency` id.
|
||||
:param str so_date: The date of the `sale.order`, to compute the price at the right rate.
|
||||
:param int|None company_id: The company to use, as a `res.company` id.
|
||||
:param int|None pricelist_id: The pricelist to use, as a `product.pricelist` id.
|
||||
:param dict kwargs: Locally unused data passed to `_get_product_information`.
|
||||
:rtype: [dict]
|
||||
:return: A list of optional products information, generated by
|
||||
:meth:`_get_product_information`.
|
||||
"""
|
||||
if company_id:
|
||||
request.update_context(allowed_company_ids=[company_id])
|
||||
product_template = self._get_product_template(product_template_id)
|
||||
parent_combination = request.env['product.template.attribute.value'].browse(
|
||||
parent_ptav_ids + ptav_ids
|
||||
)
|
||||
currency = request.env['res.currency'].browse(currency_id)
|
||||
pricelist = request.env['product.pricelist'].browse(pricelist_id)
|
||||
return [
|
||||
dict(
|
||||
**self._get_product_information(
|
||||
optional_product_template,
|
||||
optional_product_template._get_first_possible_combination(
|
||||
parent_combination=parent_combination
|
||||
),
|
||||
currency,
|
||||
pricelist,
|
||||
datetime.fromisoformat(so_date),
|
||||
parent_combination=parent_combination,
|
||||
**kwargs,
|
||||
),
|
||||
parent_product_tmpl_id=product_template.id,
|
||||
) for optional_product_template in product_template.optional_product_ids if
|
||||
self._should_show_product(optional_product_template, parent_combination)
|
||||
]
|
||||
|
||||
def _get_product_template(self, product_template_id):
|
||||
return request.env['product.template'].browse(product_template_id)
|
||||
|
||||
def _get_product_information(
|
||||
self,
|
||||
product_template,
|
||||
combination,
|
||||
currency,
|
||||
pricelist,
|
||||
so_date,
|
||||
quantity=1,
|
||||
product_uom_id=None,
|
||||
parent_combination=None,
|
||||
show_packaging=True,
|
||||
**kwargs,
|
||||
):
|
||||
"""Return complete information about a product.
|
||||
|
||||
:param product.template product_template: The product for which to seek information.
|
||||
:param product.template.attribute.value combination: The combination of the product.
|
||||
:param res.currency currency: The currency of the transaction.
|
||||
:param product.pricelist pricelist: The pricelist to use.
|
||||
:param datetime so_date: The date of the `sale.order`, to compute the price at the right
|
||||
rate.
|
||||
:param int quantity: The quantity of the product.
|
||||
:param int|None product_uom_id: The unit of measure of the product, as a `uom.uom` id.
|
||||
:param product.template.attribute.value|None parent_combination: The combination of the
|
||||
parent product.
|
||||
:param dict kwargs: Locally unused data passed to `_get_basic_product_information`.
|
||||
:rtype: dict
|
||||
:return: A dict with the following structure:
|
||||
{
|
||||
'product_tmpl_id': int,
|
||||
'id': int,
|
||||
'description_sale': str|False,
|
||||
'display_name': str,
|
||||
'price': float,
|
||||
'quantity': int
|
||||
'attribute_line': [{
|
||||
'id': int
|
||||
'attribute': {
|
||||
'id': int
|
||||
'name': str
|
||||
'display_type': str
|
||||
},
|
||||
'attribute_value': [{
|
||||
'id': int,
|
||||
'name': str,
|
||||
'price_extra': float,
|
||||
'html_color': str|False,
|
||||
'image': str|False,
|
||||
'is_custom': bool
|
||||
}],
|
||||
'selected_attribute_id': int,
|
||||
}],
|
||||
'exclusions': dict,
|
||||
'archived_combination': dict,
|
||||
'parent_exclusions': dict,
|
||||
'available_uoms': dict (optional),
|
||||
}
|
||||
"""
|
||||
uom = (
|
||||
(product_uom_id and request.env['uom.uom'].browse(product_uom_id))
|
||||
or product_template.uom_id
|
||||
)
|
||||
product = product_template._get_variant_for_combination(combination)
|
||||
attribute_exclusions = product_template._get_attribute_exclusions(
|
||||
parent_combination=parent_combination,
|
||||
combination_ids=combination.ids,
|
||||
)
|
||||
product_or_template = product or product_template
|
||||
ptals = product_template.attribute_line_ids
|
||||
attrs_map = {
|
||||
attr_data['id']: attr_data
|
||||
for attr_data in ptals.attribute_id.read(['id', 'name', 'display_type'])
|
||||
}
|
||||
ptavs = ptals.product_template_value_ids.filtered(lambda p: p.ptav_active or combination and p.id in combination.ids)
|
||||
ptavs_map = dict(zip(ptavs.ids, ptavs.read(['name', 'html_color', 'image', 'is_custom'])))
|
||||
|
||||
values = dict(
|
||||
product_tmpl_id=product_template.id,
|
||||
**self._get_basic_product_information(
|
||||
product_or_template,
|
||||
pricelist,
|
||||
combination,
|
||||
quantity=quantity,
|
||||
uom=uom,
|
||||
currency=currency,
|
||||
date=so_date,
|
||||
**kwargs,
|
||||
),
|
||||
quantity=quantity,
|
||||
uom=uom.read(['id', 'display_name'])[0],
|
||||
attribute_lines=[{
|
||||
'id': ptal.id,
|
||||
'attribute': dict(**attrs_map[ptal.attribute_id.id]),
|
||||
'attribute_values': [
|
||||
dict(
|
||||
**ptavs_map[ptav.id],
|
||||
price_extra=self._get_ptav_price_extra(
|
||||
ptav, currency, so_date, product_or_template
|
||||
),
|
||||
) for ptav in ptal.product_template_value_ids
|
||||
if ptav.ptav_active or (combination and ptav.id in combination.ids)
|
||||
],
|
||||
'selected_attribute_value_ids': combination.filtered(
|
||||
lambda c: ptal in c.attribute_line_id
|
||||
).ids,
|
||||
'create_variant': ptal.attribute_id.create_variant,
|
||||
} for ptal in product_template.attribute_line_ids],
|
||||
exclusions=attribute_exclusions['exclusions'],
|
||||
archived_combinations=attribute_exclusions['archived_combinations'],
|
||||
parent_exclusions=attribute_exclusions['parent_exclusions'],
|
||||
)
|
||||
if show_packaging and product_template._has_multiple_uoms():
|
||||
values['available_uoms'] = product_template._get_available_uoms().read(
|
||||
['id', 'display_name']
|
||||
)
|
||||
# Shouldn't be sent client-side
|
||||
values.pop('pricelist_rule_id', None)
|
||||
return values
|
||||
|
||||
def _get_basic_product_information(self, product_or_template, pricelist, combination, **kwargs):
|
||||
"""Return basic information about a product.
|
||||
|
||||
:param product.product|product.template product_or_template: The product for which to seek
|
||||
information.
|
||||
:param product.pricelist pricelist: The pricelist to use.
|
||||
:param product.template.attribute.value combination: The combination of the product.
|
||||
:param dict kwargs: Locally unused data passed to `_get_product_price`.
|
||||
:rtype: dict
|
||||
:return: A dict with the following structure:
|
||||
{
|
||||
'id': int, # if product_or_template is a record of `product.product`.
|
||||
'description_sale': str|False,
|
||||
'display_name': str,
|
||||
'price': float,
|
||||
}
|
||||
"""
|
||||
basic_information = dict(
|
||||
**product_or_template.read(['description_sale', 'display_name'])[0]
|
||||
)
|
||||
# If the product is a template, check the combination to compute the name to take dynamic
|
||||
# and no_variant attributes into account. Also, drop the id which was auto-included by the
|
||||
# search but isn't relevant since it is supposed to be the id of a `product.product` record.
|
||||
if not product_or_template.is_product_variant:
|
||||
basic_information['id'] = False
|
||||
combination_name = combination._get_combination_name()
|
||||
if combination_name:
|
||||
basic_information.update(
|
||||
display_name=f"{basic_information['display_name']} ({combination_name})"
|
||||
)
|
||||
price, pricelist_rule_id = request.env['product.template']._get_configurator_display_price(
|
||||
product_or_template.with_context(
|
||||
**product_or_template._get_product_price_context(combination)
|
||||
),
|
||||
pricelist=pricelist,
|
||||
**kwargs,
|
||||
)
|
||||
return dict(
|
||||
**basic_information,
|
||||
price=price,
|
||||
pricelist_rule_id=pricelist_rule_id,
|
||||
**request.env['product.template']._get_additional_configurator_data(
|
||||
product_or_template, pricelist=pricelist, **kwargs
|
||||
),
|
||||
)
|
||||
|
||||
def _get_ptav_price_extra(self, ptav, currency, date, product_or_template):
|
||||
"""Return the extra price for a product template attribute value.
|
||||
|
||||
:param product.template.attribute.value ptav: The product template attribute value for which
|
||||
to compute the extra price.
|
||||
:param res.currency currency: The currency to compute the extra price in.
|
||||
:param datetime date: The date to compute the extra price at.
|
||||
:param product.product|product.template product_or_template: The product on which the
|
||||
product template attribute value applies.
|
||||
:rtype: float
|
||||
:return: The extra price for the product template attribute value.
|
||||
"""
|
||||
return ptav.currency_id._convert(
|
||||
ptav.price_extra,
|
||||
currency,
|
||||
request.env.company,
|
||||
date.date(),
|
||||
)
|
||||
|
||||
def _should_show_product(self, product_template, parent_combination):
|
||||
"""Decide whether a product should be shown in the configurator.
|
||||
|
||||
:param product.template product_template: The product being checked.
|
||||
:param product.template.attribute.value parent_combination: The combination of the parent
|
||||
product.
|
||||
:rtype: bool
|
||||
:return: Whether the product should be shown in the configurator.
|
||||
"""
|
||||
return True
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import json
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class VariantController(http.Controller):
|
||||
@http.route(['/sale/get_combination_info'], type='json', auth="user", methods=['POST'])
|
||||
def get_combination_info(self, product_template_id, product_id, combination, add_qty, pricelist_id, **kw):
|
||||
combination = request.env['product.template.attribute.value'].browse(combination)
|
||||
pricelist = self._get_pricelist(pricelist_id)
|
||||
cids = request.httprequest.cookies.get('cids', str(request.env.user.company_id.id))
|
||||
allowed_company_ids = [int(cid) for cid in cids.split(',')]
|
||||
ProductTemplate = request.env['product.template'].with_context(allowed_company_ids=allowed_company_ids)
|
||||
if 'context' in kw:
|
||||
ProductTemplate = ProductTemplate.with_context(**kw.get('context'))
|
||||
product_template = ProductTemplate.browse(int(product_template_id))
|
||||
res = product_template._get_combination_info(combination, int(product_id or 0), int(add_qty or 1), pricelist)
|
||||
if 'parent_combination' in kw:
|
||||
parent_combination = request.env['product.template.attribute.value'].browse(kw.get('parent_combination'))
|
||||
if not combination.exists() and product_id:
|
||||
product = request.env['product.product'].browse(int(product_id))
|
||||
if product.exists():
|
||||
combination = product.product_template_attribute_value_ids
|
||||
res.update({
|
||||
'is_combination_possible': product_template._is_combination_possible(combination=combination, parent_combination=parent_combination),
|
||||
'parent_exclusions': product_template._get_parent_attribute_exclusions(parent_combination=parent_combination)
|
||||
})
|
||||
return res
|
||||
|
||||
@http.route(['/sale/create_product_variant'], type='json', auth="user", methods=['POST'])
|
||||
def create_product_variant(self, product_template_id, product_template_attribute_value_ids, **kwargs):
|
||||
return request.env['product.template'].browse(int(product_template_id)).create_product_variant(json.loads(product_template_attribute_value_ids))
|
||||
|
||||
def _get_pricelist(self, pricelist_id, pricelist_fallback=False):
|
||||
return request.env['product.pricelist'].browse(int(pricelist_id or 0))
|
||||
19
odoo-bringout-oca-ocb-sale/sale/data/ir_config_parameter.xml
Normal file
19
odoo-bringout-oca-ocb-sale/sale/data/ir_config_parameter.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo noupdate="1">
|
||||
|
||||
<record id="default_confirmation_template" model="ir.config_parameter">
|
||||
<field name="key">sale.default_confirmation_template</field>
|
||||
<field name="value" ref="sale.mail_template_sale_confirmation"/>
|
||||
</record>
|
||||
|
||||
<record id="default_invoice_email_template" model="ir.config_parameter">
|
||||
<field name="key">sale.default_invoice_email_template</field>
|
||||
<field name="value" ref="account.email_template_edi_invoice"/>
|
||||
</record>
|
||||
|
||||
<record id="async_emails" model="ir.config_parameter">
|
||||
<field name="key">sale.async_emails</field>
|
||||
<field name="value">False</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
26
odoo-bringout-oca-ocb-sale/sale/data/ir_cron.xml
Normal file
26
odoo-bringout-oca-ocb-sale/sale/data/ir_cron.xml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="1">
|
||||
|
||||
<record id="send_invoice_cron" model="ir.cron">
|
||||
<field name="name">automatic invoicing: send ready invoice</field>
|
||||
<field name="model_id" ref="payment.model_payment_transaction"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._cron_send_invoice()</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="active">False</field>
|
||||
</record>
|
||||
|
||||
<record id="send_pending_emails_cron" model="ir.cron">
|
||||
<field name="name">Sales: Send pending emails</field>
|
||||
<field name="model_id" ref="model_sale_order"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._cron_send_pending_emails()</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="active">False</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- Activities -->
|
||||
<record id="mail_act_sale_upsell" model="mail.activity.type">
|
||||
<field name="name">Order Upsell</field>
|
||||
<field name="icon">fa-line-chart</field>
|
||||
<field name="res_model">sale.order</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -14,6 +14,12 @@
|
|||
<field name="default" eval="False"/>
|
||||
<field name="description">Quotation confirmed</field>
|
||||
</record>
|
||||
<record id="mt_order_viewed" model="mail.message.subtype">
|
||||
<field name="name">Quotation Viewed</field>
|
||||
<field name="res_model">sale.order</field>
|
||||
<field name="internal" eval="True"/>
|
||||
<field name="default" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- Salesteam-related subtypes for messaging / Chatter -->
|
||||
<record id="mt_salesteam_order_sent" model="mail.message.subtype">
|
||||
|
|
@ -24,24 +30,33 @@
|
|||
<field name="parent_id" ref="sale.mt_order_sent"/>
|
||||
<field name="relation_field">team_id</field>
|
||||
</record>
|
||||
<record id="mt_salesteam_order_viewed" model="mail.message.subtype">
|
||||
<field name="name">Quotation Viewed</field>
|
||||
<field name="sequence">25</field>
|
||||
<field name="res_model">crm.team</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="parent_id" ref="sale.mt_order_viewed"/>
|
||||
<field name="relation_field">team_id</field>
|
||||
</record>
|
||||
<record id="mt_salesteam_order_confirmed" model="mail.message.subtype">
|
||||
<field name="name">Sales Order Confirmed</field>
|
||||
<field name="sequence">21</field>
|
||||
<field name="sequence">30</field>
|
||||
<field name="res_model">crm.team</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="parent_id" ref="sale.mt_order_confirmed"/>
|
||||
<field name="relation_field">team_id</field>
|
||||
</record>
|
||||
<record id="mt_salesteam_invoice_created" model="mail.message.subtype">
|
||||
<field name="name">Invoice Created</field>
|
||||
<field name="sequence">22</field>
|
||||
<record id="mt_salesteam_invoice_paid" model="mail.message.subtype">
|
||||
<field name="name">Invoice Paid</field>
|
||||
<field name="sequence">35</field>
|
||||
<field name="res_model">crm.team</field>
|
||||
<field name="parent_id" ref="account.mt_invoice_created"/>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="parent_id" ref="account.mt_invoice_paid"/>
|
||||
<field name="relation_field">team_id</field>
|
||||
</record>
|
||||
<record id="mt_salesteam_invoice_confirmed" model="mail.message.subtype">
|
||||
<field name="name">Invoice Confirmed</field>
|
||||
<field name="sequence">23</field>
|
||||
<record id="mt_salesteam_invoice_posted" model="mail.message.subtype">
|
||||
<field name="name">Invoice Posted</field>
|
||||
<field name="sequence">40</field>
|
||||
<field name="res_model">crm.team</field>
|
||||
<field name="parent_id" ref="account.mt_invoice_validated"/>
|
||||
<field name="relation_field">team_id</field>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@
|
|||
<record id="email_template_edi_sale" model="mail.template">
|
||||
<field name="name">Sales: Send Quotation</field>
|
||||
<field name="model_id" ref="sale.model_sale_order"/>
|
||||
<field name="subject">{{ object.company_id.name }} {{ object.state in ('draft', 'sent') and (ctx.get('proforma') and 'Proforma' or 'Quotation') or 'Order' }} (Ref {{ object.name or 'n/a' }})</field>
|
||||
<field name="subject">{{ object.company_id.name }} {{ object.state in ('draft', 'sent') and 'Quotation' or 'Order' }} (Ref {{ object.name or 'n/a' }})</field>
|
||||
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
||||
<field name="partner_to">{{ object.partner_id.id }}</field>
|
||||
<field name="partner_to" eval="False"/>
|
||||
<field name="use_default_to" eval="True"/>
|
||||
<field name="description">Used by salespeople when they send quotations or proforma to prospects</field>
|
||||
<field name="body_html" type="html">
|
||||
<div style="margin: 0px; padding: 0px;">
|
||||
|
|
@ -14,34 +15,71 @@
|
|||
<t t-set="doc_name" t-value="'quotation' if object.state in ('draft', 'sent') else 'order'"/>
|
||||
Hello,
|
||||
<br/><br/>
|
||||
Your
|
||||
<t t-if="ctx.get('proforma')">
|
||||
Pro forma invoice for <t t-out="doc_name or ''">quotation</t> <span style="font-weight: bold;" t-out="object.name or ''">S00052</span>
|
||||
<t t-if="object.origin">
|
||||
(with reference: <t t-out="object.origin or ''"></t> )
|
||||
</t>
|
||||
amounting in <span style="font-weight: bold;" t-out="format_amount(object.amount_total, object.pricelist_id.currency_id) or ''">$ 10.00</span> is available.
|
||||
Your <t t-out="doc_name or ''">quotation</t> <span style="font-weight: bold;" t-out="object.name or ''"></span>
|
||||
<t t-if="object.origin">
|
||||
(with reference: <t t-out="object.origin or ''">S00052</t> )
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-out="doc_name or ''">quotation</t> <span style="font-weight: bold;" t-out="object.name or ''"></span>
|
||||
<t t-if="object.origin">
|
||||
(with reference: <t t-out="object.origin or ''">S00052</t> )
|
||||
amounting in <span style="font-weight: bold;" t-out="format_amount(object.amount_total, object.currency_id) or ''">$ 10.00</span> is ready for review.
|
||||
<br/>
|
||||
<t t-set="documents" t-value="object._get_product_documents()"/>
|
||||
<t t-if="documents">
|
||||
<br/>
|
||||
<t t-if="len(documents)>1">
|
||||
Here are some additional documents that may interest you:
|
||||
</t>
|
||||
amounting in <span style="font-weight: bold;" t-out="format_amount(object.amount_total, object.pricelist_id.currency_id) or ''">$ 10.00</span> is ready for review.
|
||||
<t t-else="">
|
||||
Here is an additional document that may interest you:
|
||||
</t>
|
||||
<ul style="margin-bottom: 0;">
|
||||
<t t-foreach="documents" t-as="document">
|
||||
<li style="font-size: 13px;">
|
||||
<a t-out="document.ir_attachment_id.name"
|
||||
t-att-href="object.get_portal_url('/document/' + str(document.id))"
|
||||
t-att-target="target"/>
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
</t>
|
||||
<br/><br/>
|
||||
<br/>
|
||||
Do not hesitate to contact us if you have any questions.
|
||||
<t t-if="not is_html_empty(object.user_id.signature)">
|
||||
<br/><br/>
|
||||
<t t-out="object.user_id.signature or ''">--<br/>Mitchell Admin</t>
|
||||
<div>--<br/><t t-out="object.user_id.signature or ''">Mitchell Admin</t></div>
|
||||
</t>
|
||||
<br/><br/>
|
||||
</p>
|
||||
</div>
|
||||
</field>
|
||||
<field name="report_template" ref="action_report_saleorder"/>
|
||||
<field name="report_name">{{ (object.name or '').replace('/','_') }}</field>
|
||||
<field name="lang">{{ object.partner_id.lang }}</field>
|
||||
<field name="report_template_ids" eval="[(4, ref('sale.action_report_saleorder'))]"/>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="email_template_proforma" model="mail.template">
|
||||
<field name="name">Sales: Send Proforma</field>
|
||||
<field name="model_id" ref="sale.model_sale_order"/>
|
||||
<field name="subject">{{ object.company_id.name }} {{ object.state in ('draft', 'sent') and 'Proforma' or 'Order'}} (Ref {{ object.name or 'n/a' }})</field>
|
||||
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
||||
<field name="partner_to" eval="False"/>
|
||||
<field name="use_default_to" eval="True"/>
|
||||
<field name="description">Used by salespeople when they send proforma to prospects</field>
|
||||
<field name="body_html" type="html">
|
||||
<div style="margin: 0px; padding: 0px;">
|
||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
<t t-set="doc_name" t-value="'quotation' if object.state in ('draft', 'sent') else 'order'"/>
|
||||
Hello,
|
||||
<br/><br/>
|
||||
Your Pro forma invoice for <t t-out="doc_name or ''">quotation</t> <span style="font-weight: bold;" t-out="object.name or ''">S00052</span>
|
||||
<t t-if="object.origin">
|
||||
(with reference: <t t-out="object.origin or ''"></t> )
|
||||
</t>
|
||||
amounting in <span style="font-weight: bold;" t-out="format_amount(object.amount_total, object.currency_id) or ''">$ 10.00</span> is available.
|
||||
<br/><br/>
|
||||
Do not hesitate to contact us if you have any questions.
|
||||
<t t-if="not is_html_empty(object.user_id.signature)">
|
||||
<div>--<br/><t t-out="object.user_id.signature or ''">Mitchell Admin</t></div>
|
||||
</t>
|
||||
</p>
|
||||
</div>
|
||||
</field>
|
||||
<field name="report_template_ids" eval="[(4, ref('sale.action_report_pro_forma_invoice'))]"/>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
|
|
@ -50,59 +88,97 @@
|
|||
<field name="model_id" ref="sale.model_sale_order"/>
|
||||
<field name="subject">{{ object.company_id.name }} {{ (object.get_portal_last_transaction().state == 'pending') and 'Pending Order' or 'Order' }} (Ref {{ object.name or 'n/a' }})</field>
|
||||
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
||||
<field name="partner_to">{{ object.partner_id.id }}</field>
|
||||
<field name="partner_to" eval="False"/>
|
||||
<field name="use_default_to" eval="True"/>
|
||||
<field name="description">Sent to customers on order confirmation</field>
|
||||
<field name="body_html" type="html">
|
||||
<div style="margin: 0px; padding: 0px;">
|
||||
<p style="margin: 0px; padding: 0px; font-size: 12px;">
|
||||
Hello,
|
||||
<br/><br/>
|
||||
<t t-set="transaction" t-value="object.get_portal_last_transaction()"/>
|
||||
<t t-set="tx_sudo" t-value="object.get_portal_last_transaction()"/>
|
||||
Your order <span style="font-weight:bold;" t-out="object.name or ''">S00049</span> amounting in <span style="font-weight:bold;" t-out="format_amount(object.amount_total, object.currency_id) or ''">$ 10.00</span>
|
||||
<t t-if="object.state == 'sale' or (transaction and transaction.state in ('done', 'authorized'))">
|
||||
<t t-if="object.state == 'sale' or (tx_sudo and tx_sudo.state in ('done', 'authorized'))">
|
||||
has been confirmed.<br/>
|
||||
Thank you for your trust!
|
||||
</t>
|
||||
<t t-elif="transaction and transaction.state == 'pending'">
|
||||
<t t-elif="tx_sudo and tx_sudo.state == 'pending'">
|
||||
is pending. It will be confirmed when the payment is received.
|
||||
<t t-if="object.reference">
|
||||
Your payment reference is <span style="font-weight:bold;" t-out="object.reference or ''"></span>.
|
||||
</t>
|
||||
</t>
|
||||
<br/><br/>
|
||||
<br/>
|
||||
<t t-set="documents" t-value="object._get_product_documents()"/>
|
||||
<t t-if="documents">
|
||||
<br/>
|
||||
<t t-if="len(documents)>1">
|
||||
Here are some additional documents that may interest you:
|
||||
</t>
|
||||
<t t-else="">
|
||||
Here is an additional document that may interest you:
|
||||
</t>
|
||||
<ul style="margin-bottom: 0;">
|
||||
<t t-foreach="documents" t-as="document">
|
||||
<li style="font-size: 13px;">
|
||||
<a t-out="document.ir_attachment_id.name"
|
||||
t-att-href="object.get_portal_url('/document/' + str(document.id))"
|
||||
t-att-target="target"/>
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
</t>
|
||||
<br/>
|
||||
Do not hesitate to contact us if you have any questions.
|
||||
<t t-if="not is_html_empty(object.user_id.signature)">
|
||||
<br/><br/>
|
||||
<t t-out="object.user_id.signature or ''">--<br/>Mitchell Admin</t>
|
||||
<div>--<br/><t t-out="object.user_id.signature or ''">Mitchell Admin</t></div>
|
||||
</t>
|
||||
<br/><br/>
|
||||
</p>
|
||||
<t t-if="hasattr(object, 'website_id') and object.website_id">
|
||||
<div style="margin: 0px; padding: 0px;">
|
||||
<table width="100%" style="color: #454748; font-size: 12px; border-collapse: collapse;">
|
||||
<table width="100%" style="color: #454748; font-size: 12px; border-collapse: collapse; white-space: nowrap;">
|
||||
<tr style="border-bottom: 2px solid #dee2e6;">
|
||||
<td style="width: 150px;"><span style="font-weight:bold;">Products</span></td>
|
||||
<td></td>
|
||||
<td width="15%" align="center"><span style="font-weight:bold;">Quantity</span></td>
|
||||
<td width="20%" align="right"><span style="font-weight:bold;">
|
||||
<t t-if="object.user_id.has_group('account.group_show_line_subtotals_tax_excluded')">
|
||||
VAT Excl.
|
||||
</t>
|
||||
<t t-else="">
|
||||
VAT Incl.
|
||||
</t>
|
||||
</span></td>
|
||||
<td width="20%" align="right">
|
||||
<span style="font-weight:bold;">
|
||||
<t t-if="hasattr(object, 'website_id') and object.website_id.show_line_subtotals_tax_selection == 'tax_excluded'">
|
||||
Tax Excl.
|
||||
</t>
|
||||
<t t-else="">
|
||||
Tax Incl.
|
||||
</t>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
<t t-foreach="object.order_line" t-as="line">
|
||||
<t t-if="(not hasattr(line, 'is_delivery') or not line.is_delivery) and line.display_type in ['line_section', 'line_note']">
|
||||
<t
|
||||
t-set="line_subtotal"
|
||||
t-value="
|
||||
line.price_subtotal
|
||||
if hasattr(object, 'website_id') and object.website_id.show_line_subtotals_tax_selection == 'tax_excluded'
|
||||
else line.price_total
|
||||
"
|
||||
/>
|
||||
<t t-set="current_subtotal" t-value="current_subtotal + line_subtotal"/>
|
||||
<t
|
||||
t-if="(not hasattr(line, 'is_delivery') or not line.is_delivery) and (
|
||||
line.display_type in ['line_section', 'line_subsection', 'line_note']
|
||||
or line.product_type == 'combo'
|
||||
)"
|
||||
>
|
||||
<table width="100%" style="color: #454748; font-size: 12px; border-collapse: collapse;">
|
||||
<t t-set="loop_cycle_number" t-value="loop_cycle_number or 0" />
|
||||
<tr t-att-style="'background-color: #f2f2f2' if loop_cycle_number % 2 == 0 else 'background-color: #ffffff'">
|
||||
<t t-set="loop_cycle_number" t-value="loop_cycle_number + 1" />
|
||||
<td colspan="4">
|
||||
<t t-if="line.display_type == 'line_section'">
|
||||
<span style="font-weight:bold;" t-out="line.name or ''">Taking care of Trees Course</span>
|
||||
<t t-if="line.display_type in ('line_section', 'line_subsection') or line.product_type == 'combo'">
|
||||
<span t-att-style="'font-weight:bold;' if line.display_type == 'line_subsection' else 'font-weight:bolder;'" t-out="line.name or ''">Taking care of Trees Course</span>
|
||||
<t t-set="current_section" t-value="line"/>
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
</t>
|
||||
<t t-elif="line.display_type == 'line_note'">
|
||||
<i t-out="line.name or ''">Taking care of Trees Course</i>
|
||||
|
|
@ -117,12 +193,16 @@
|
|||
<tr t-att-style="'background-color: #f2f2f2' if loop_cycle_number % 2 == 0 else 'background-color: #ffffff'">
|
||||
<t t-set="loop_cycle_number" t-value="loop_cycle_number + 1" />
|
||||
<td style="width: 150px;">
|
||||
<img t-attf-src="/web/image/product.product/{{ line.product_id.id }}/image_128" style="width: 64px; height: 64px; object-fit: contain;" alt="Product image"></img>
|
||||
<img
|
||||
t-attf-src="/web/image/product.product/{{ line.product_id.id }}/image_128"
|
||||
t-attf-style="width: 64px; height: {{hasattr(object, 'website_id') and object.website_id and object.website_id._get_product_image_ratio_height() or '64px'}}; object-fit: cover; object-position: center;"
|
||||
alt="Product image"
|
||||
/>
|
||||
</td>
|
||||
<td align="left" t-out="line.product_id.name or ''"> Taking care of Trees Course</td>
|
||||
<td width="15%" align="center" t-out="line.product_uom_qty or ''">1</td>
|
||||
<td width="20%" align="right"><span style="font-weight:bold;">
|
||||
<t t-if="object.user_id.has_group('account.group_show_line_subtotals_tax_excluded')">
|
||||
<td width="20%" align="right"><span style="font-weight:bold; white-space: nowrap;">
|
||||
<t t-if="hasattr(object, 'website_id') and object.website_id.show_line_subtotals_tax_selection == 'tax_excluded'">
|
||||
<t t-out="format_amount(line.price_reduce_taxexcl, object.currency_id) or ''">$ 10.00</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
|
|
@ -132,10 +212,33 @@
|
|||
</tr>
|
||||
</table>
|
||||
</t>
|
||||
<t
|
||||
t-if="current_section and (
|
||||
line_last
|
||||
or object.order_line[line_index+1].display_type in ('line_section', 'line_subsection')
|
||||
or object.order_line[line_index+1].product_type == 'combo'
|
||||
or (
|
||||
line.combo_item_id
|
||||
and not object.order_line[line_index+1].combo_item_id
|
||||
)
|
||||
) and not line.is_downpayment"
|
||||
>
|
||||
<t t-set="current_section" t-value="None"/>
|
||||
<table width="100%" style="color: #454748; font-size: 12px; border-collapse: collapse;">
|
||||
<t t-set="loop_cycle_number" t-value="loop_cycle_number or 0"/>
|
||||
<tr t-att-style="'background-color: #f2f2f2' if loop_cycle_number % 2 == 0 else 'background-color: #ffffff'">
|
||||
<t t-set="loop_cycle_number" t-value="loop_cycle_number + 1"/>
|
||||
<td style="width: 100%" align="right">
|
||||
<span style="font-weight: bold;">Subtotal:</span>
|
||||
<span t-out="format_amount(current_subtotal, object.currency_id) or ''">$ 10.00</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
<div style="margin: 0px; padding: 0px;" t-if="hasattr(object, 'carrier_id') and object.carrier_id">
|
||||
<table width="100%" style="color: #454748; font-size: 12px; border-spacing: 0px 4px;" align="right">
|
||||
<table width="100%" style="color: #454748; font-size: 12px; border-spacing: 0px 4px; white-space: nowrap;" align="right">
|
||||
<tr>
|
||||
<td style="width: 60%"/>
|
||||
<td style="width: 30%; border-top: 1px solid #dee2e6;" align="right"><span style="font-weight:bold;">Delivery:</span></td>
|
||||
|
|
@ -143,22 +246,22 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td style="width: 60%"/>
|
||||
<td style="width: 30%;" align="right"><span style="font-weight:bold;">SubTotal:</span></td>
|
||||
<td style="width: 30%;" align="right"><span style="font-weight:bold;">Untaxed Amount:</span></td>
|
||||
<td style="width: 10%;" align="right" t-out="format_amount(object.amount_untaxed, object.currency_id) or ''">$ 10.00</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div style="margin: 0px; padding: 0px;" t-else="">
|
||||
<table width="100%" style="color: #454748; font-size: 12px; border-spacing: 0px 4px;" align="right">
|
||||
<table width="100%" style="color: #454748; font-size: 12px; border-spacing: 0px 4px; white-space: nowrap;" align="right">
|
||||
<tr>
|
||||
<td style="width: 60%"/>
|
||||
<td style="width: 30%; border-top: 1px solid #dee2e6;" align="right"><span style="font-weight:bold;">SubTotal:</span></td>
|
||||
<td style="width: 30%; border-top: 1px solid #dee2e6;" align="right"><span style="font-weight:bold;">Untaxed Amount:</span></td>
|
||||
<td style="width: 10%; border-top: 1px solid #dee2e6;" align="right" t-out="format_amount(object.amount_untaxed, object.currency_id) or ''">$ 10.00</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div style="margin: 0px; padding: 0px;">
|
||||
<table width="100%" style="color: #454748; font-size: 12px; border-spacing: 0px 4px;" align="right">
|
||||
<table width="100%" style="color: #454748; font-size: 12px; border-spacing: 0px 4px; white-space: nowrap;" align="right">
|
||||
<tr>
|
||||
<td style="width: 60%"/>
|
||||
<td style="width: 30%;" align="right"><span style="font-weight:bold;">Taxes:</span></td>
|
||||
|
|
@ -186,18 +289,18 @@
|
|||
<tr>
|
||||
<td>
|
||||
<span style="font-weight:bold;">Payment Method:</span>
|
||||
<t t-if="transaction.token_id">
|
||||
<t t-out="transaction.token_id.display_name or ''"></t>
|
||||
<t t-if="tx_sudo.token_id">
|
||||
<t t-out="tx_sudo.token_id.display_name or ''"></t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-out="transaction.provider_id.sudo().name or ''"></t>
|
||||
<t t-out="tx_sudo.provider_id.sudo().name or ''"></t>
|
||||
</t>
|
||||
(<t t-out="format_amount(transaction.amount, object.currency_id) or ''">$ 10.00</t>)
|
||||
(<t t-out="format_amount(tx_sudo.amount, object.currency_id) or ''">$ 10.00</t>)
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div t-if="object.partner_shipping_id and not object.only_services" style="margin: 0px; padding: 0px;">
|
||||
<div t-if="hasattr(object, 'only_services') and object.partner_shipping_id and not object.only_services" style="margin: 0px; padding: 0px;">
|
||||
<table width="100%" style="color: #454748; font-size: 12px;">
|
||||
<tr>
|
||||
<td>
|
||||
|
|
@ -234,39 +337,61 @@
|
|||
</div>
|
||||
</t>
|
||||
</div></field>
|
||||
<field name="report_template" ref="action_report_saleorder"/>
|
||||
<field name="report_name">{{ (object.name or '').replace('/','_') }}</field>
|
||||
<field name="lang">{{ object.partner_id.lang }}</field>
|
||||
<field name="report_template_ids" eval="[(4, ref('sale.action_report_saleorder'))]"/>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="sale.mail_template_sale_cancellation" model="mail.template">
|
||||
<field name="name">Sales: Order Cancellation</field>
|
||||
<record id="mail_template_sale_payment_executed" model="mail.template">
|
||||
<field name="name">Sales: Payment Done</field>
|
||||
<field name="model_id" ref="sale.model_sale_order"/>
|
||||
<field name="subject">{{ object.company_id.name }} {{ object.type_name }} Cancelled (Ref {{ object.name or 'n/a' }})</field>
|
||||
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
||||
<field name="partner_to">{{ object.partner_id.id }}</field>
|
||||
<field name="description">Sent automatically to customers when you cancel an order</field>
|
||||
<field name="subject">{{ object.company_id.name }} {{ (object.get_portal_last_transaction().state == 'pending') and 'Pending Order' or 'Order' }} (Ref {{ object.name or 'n/a' }})</field>
|
||||
<field name="email_from">{{ (object.user_id.email_formatted or user.email_formatted) }}</field>
|
||||
<field name="partner_to" eval="False"/>
|
||||
<field name="use_default_to" eval="True"/>
|
||||
<field name="description">Sent to customers when a payment is received but doesn't immediately confirm their order</field>
|
||||
<field name="body_html" type="html">
|
||||
<div style="margin: 0px; padding: 0px;">
|
||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
<t t-set="doc_name" t-value="object.type_name"/>
|
||||
Dear <t t-out="object.partner_id.name or ''">user</t>,
|
||||
<p style="margin: 0px; padding: 0px; font-size: 12px;">
|
||||
<t t-set="transaction_sudo" t-value="object.get_portal_last_transaction()"/>
|
||||
Hello,
|
||||
<br/><br/>
|
||||
Please be advised that your
|
||||
<t t-out="doc_name or ''">quotation</t> <strong t-out="object.name or ''">S00052</strong>
|
||||
<t t-if="object.origin">
|
||||
(with reference: <t t-out="object.origin or ''">S00052</t> )
|
||||
A payment with reference
|
||||
<span style="font-weight:bold;" t-out="transaction_sudo.reference or ''">SOOO49</span>
|
||||
amounting
|
||||
<span style="font-weight:bold;" t-out="format_amount(transaction_sudo.amount, object.currency_id) or ''">$ 10.00</span>
|
||||
for your order
|
||||
<span style="font-weight:bold;" t-out="object.name or ''">S00049</span>
|
||||
<t t-if="transaction_sudo and transaction_sudo.state == 'pending'">
|
||||
is pending.
|
||||
<br/>
|
||||
<t t-if="object.currency_id.compare_amounts(object.amount_paid + transaction_sudo.amount, object.amount_total) >= 0 and object.state in ('draft', 'sent')">
|
||||
Your order will be confirmed once the payment is confirmed.
|
||||
</t>
|
||||
<t t-else="">
|
||||
Once confirmed,
|
||||
<span style="font-weight:bold;" t-out="format_amount(object.amount_total - object.amount_paid - transaction_sudo.amount, object.currency_id) or ''">$ 10.00</span>
|
||||
will remain to be paid.
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
has been confirmed.
|
||||
<t t-if="object.currency_id.compare_amounts(object.amount_paid, object.amount_total) < 0">
|
||||
<br/>
|
||||
<span style="font-weight:bold;" t-out="format_amount(object.amount_total - object.amount_paid, object.currency_id) or ''">$ 10.00</span>
|
||||
remains to be paid.
|
||||
</t>
|
||||
</t>
|
||||
has been cancelled. Therefore, you should not be charged further for this order.
|
||||
If any refund is necessary, this will be executed at best convenience.
|
||||
<br/><br/>
|
||||
Do not hesitate to contact us if you have any questions.
|
||||
Thank you for your trust!
|
||||
<br/>
|
||||
Do not hesitate to contact us if you have any questions.
|
||||
<t t-if="not is_html_empty(object.user_id.signature)">
|
||||
<div>--<br/><t t-out="object.user_id.signature or ''">Mitchell Admin</t></div>
|
||||
</t>
|
||||
</p>
|
||||
</div>
|
||||
</field>
|
||||
<field name="lang">{{ object.partner_id.lang }}</field>
|
||||
<field name="report_template_ids" eval="[(4, ref('sale.action_report_saleorder'))]"/>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
|
|
|
|||
|
|
@ -125,109 +125,48 @@
|
|||
<field name="expense_policy">cost</field>
|
||||
</record>
|
||||
|
||||
<record id="product.product_attribute_2" model="product.attribute">
|
||||
<field name="display_type">color</field>
|
||||
</record>
|
||||
<record id="product.product_attribute_3" model="product.attribute">
|
||||
<field name="display_type">select</field>
|
||||
</record>
|
||||
|
||||
<record id="product.product_attribute_value_3" model="product.attribute.value">
|
||||
<field name="html_color">#FFFFFF</field>
|
||||
</record>
|
||||
<record id="product.product_attribute_value_4" model="product.attribute.value">
|
||||
<field name="html_color">#000000</field>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_value_7" model="product.attribute.value">
|
||||
<field name="name">Custom</field>
|
||||
<field name="attribute_id" ref="product.product_attribute_1"/>
|
||||
<field name="is_custom">True</field>
|
||||
<field name="sequence">3</field>
|
||||
</record>
|
||||
|
||||
<record id="product.product_4_attribute_1_product_template_attribute_line" model="product.template.attribute.line">
|
||||
<field name="value_ids" eval="[(4,ref('product_attribute_value_7'))]"/>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
Handle automatically created product.template.attribute.value.
|
||||
Check "product.product_4_attribute_1_value_2" for more information about this
|
||||
-->
|
||||
<function model="ir.model.data" name="_update_xmlids">
|
||||
<value model="base" eval="[{
|
||||
'xml_id': 'sale.product_4_attribute_1_value_3',
|
||||
'record': obj().env.ref('product.product_4_attribute_1_product_template_attribute_line').product_template_value_ids[2],
|
||||
'noupdate': True,
|
||||
}]"/>
|
||||
</function>
|
||||
|
||||
<function model="ir.model.data" name="_update_xmlids">
|
||||
<value model="base" eval="[{
|
||||
'xml_id': 'sale.product_product_4e',
|
||||
'record': obj().env.ref('product.product_product_4_product_template')._get_variant_for_combination(obj().env.ref('sale.product_4_attribute_1_value_3') + obj().env.ref('product.product_4_attribute_2_value_1')),
|
||||
'noupdate': True,
|
||||
}, {
|
||||
'xml_id': 'sale.product_product_4f',
|
||||
'record': obj().env.ref('product.product_product_4_product_template')._get_variant_for_combination(obj().env.ref('sale.product_4_attribute_1_value_3') + obj().env.ref('product.product_4_attribute_2_value_2')),
|
||||
'noupdate': True,
|
||||
},]"/>
|
||||
</function>
|
||||
|
||||
<record id="product_product_4e" model="product.product">
|
||||
<record id="product.product_product_4e" model="product.product">
|
||||
<field name="default_code">DESK0005</field>
|
||||
<field name="weight">0.01</field>
|
||||
</record>
|
||||
|
||||
<record id="product_product_4f" model="product.product">
|
||||
<record id="product.product_product_4f" model="product.product">
|
||||
<field name="default_code">DESK0006</field>
|
||||
<field name="weight">0.01</field>
|
||||
</record>
|
||||
|
||||
<record id="product_template_attribute_exclusion_1" model="product.template.attribute.exclusion">
|
||||
<field name="product_tmpl_id" ref="product.product_product_4_product_template" />
|
||||
<field name="value_ids" eval="[(6,0,[ref('product.product_4_attribute_2_value_2')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_template_attribute_exclusion_2" model="product.template.attribute.exclusion">
|
||||
<field name="product_tmpl_id" ref="product.product_product_11_product_template" />
|
||||
<field name="value_ids" eval="[(6,0,[ref('product.product_11_attribute_1_value_1')])]"/>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
The "Customizable Desk's Aluminium" attribute value will excude:
|
||||
- The "Customizable Desk's Black" attribute
|
||||
- The "Office Chair's Steel" attribute
|
||||
-->
|
||||
<record id="product.product_4_attribute_1_value_2" model="product.template.attribute.value">
|
||||
<field name="exclude_for" eval="[(6,0,[ref('sale.product_template_attribute_exclusion_1') ,ref('sale.product_template_attribute_exclusion_2')])]" />
|
||||
</record>
|
||||
|
||||
<record id="product_template_attribute_exclusion_3" model="product.template.attribute.exclusion">
|
||||
<field name="product_tmpl_id" ref="product.product_product_11_product_template" />
|
||||
<field name="value_ids" eval="[(6,0,[ref('product.product_11_attribute_1_value_2')])]"/>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
The "Customizable Desk's Steel" attribute value will excude:
|
||||
- The "Office Chair's Aluminium" attribute
|
||||
-->
|
||||
<record id="product.product_4_attribute_1_value_1" model="product.template.attribute.value">
|
||||
<field name="exclude_for" eval="[(6,0,[ref('sale.product_template_attribute_exclusion_3')])]" />
|
||||
</record>
|
||||
|
||||
<record id="advance_product_0" model="product.product">
|
||||
<field name="name">Deposit</field>
|
||||
<field name="categ_id" ref="product.product_category_3"/>
|
||||
<field name="categ_id" eval="ref('product.product_category_services', raise_if_not_found=False)"/>
|
||||
<field name="type">service</field>
|
||||
<field name="list_price">150.0</field>
|
||||
<field name="invoice_policy">order</field>
|
||||
<field name="standard_price">100.0</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="company_id" eval="[]"/>
|
||||
<field name="image_1920" type="base64" file="sale/static/img/advance_product_0-image.jpg"/>
|
||||
<field name="taxes_id" eval="[]"/>
|
||||
<field name="supplier_taxes_id" eval="[]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_1_product_template" model="product.template">
|
||||
<field name="name">Chair floor protection</field>
|
||||
<field name="categ_id" ref="product.product_category_office"/>
|
||||
<field name="list_price">12.0</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="description_sale">Office chairs can harm your floor: protect it</field>
|
||||
<field name="image_1920" type="base64" file="sale/static/img/floor_protection-image.jpg"/>
|
||||
</record>
|
||||
|
||||
<record id="product.product_product_4_product_template" model="product.template">
|
||||
<field name="optional_product_ids" eval="[Command.set([ref('product.product_product_11_product_template')])]"/>
|
||||
</record>
|
||||
<record id="product.product_product_11_product_template" model="product.template">
|
||||
<field name="optional_product_ids" eval="[Command.set([ref('product_product_1_product_template')])]"/>
|
||||
</record>
|
||||
<record id="product.product_product_13_product_template" model="product.template">
|
||||
<field name="optional_product_ids" eval="[Command.set([ref('product.product_product_11_product_template')])]"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="1">
|
||||
|
||||
<!-- TODO remove as it is already the fallback in _find_mail_template -->
|
||||
<record id="default_confirmation_template" model="ir.config_parameter">
|
||||
<field name="key">sale.default_confirmation_template</field>
|
||||
<field name="value" ref="sale.mail_template_sale_confirmation"/>
|
||||
</record>
|
||||
|
||||
<record id="default_invoice_email_template" model="ir.config_parameter">
|
||||
<field name="key">sale.default_invoice_email_template</field>
|
||||
<field name="value" ref="account.email_template_edi_invoice"/>
|
||||
</record>
|
||||
|
||||
<record id="send_invoice_cron" model="ir.cron">
|
||||
<field name="name">automatic invoicing: send ready invoice</field>
|
||||
<field name="model_id" ref="payment.model_payment_transaction" />
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._cron_send_invoice()</field>
|
||||
<field name="user_id" ref="base.user_root" />
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -1,31 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="1">
|
||||
|
||||
<!-- Enable EUR currency since it's the currency on the company, pricelist and Sale Orders
|
||||
|
||||
If the currency is not enabled, you cannot pay the demo SO's with a payment link bc the currency
|
||||
is disabled.
|
||||
-->
|
||||
<function model="res.currency" name="action_unarchive">
|
||||
<value model="res.currency" search="[('id', '=', obj().env.ref('product.list0').currency_id.id), ('active', '=', False)]"/>
|
||||
</function>
|
||||
|
||||
<!-- We want to activate pay and sign by default for easier demoing. -->
|
||||
<record id="base.main_company" model="res.company">
|
||||
<field name="portal_confirmation_pay" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="base.user_demo" model="res.users">
|
||||
<field eval="[(4, ref('sales_team.group_sale_salesman'))]" name="groups_id"/>
|
||||
<field eval="[(4, ref('sales_team.group_sale_salesman'))]" name="group_ids"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.team" id="sales_team.team_sales_department">
|
||||
<field name="use_quotations" eval="True"/>
|
||||
<field name="invoiced_target">250000</field>
|
||||
</record>
|
||||
|
||||
<record model="crm.team" id="sales_team.crm_team_1">
|
||||
<field name="use_quotations" eval="True"/>
|
||||
<field name="invoiced_target">40000</field>
|
||||
</record>
|
||||
|
||||
|
|
@ -38,7 +27,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_2"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_2"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -72,7 +60,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_address_13"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_13"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -100,7 +87,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_4"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_4"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -125,7 +111,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -164,7 +149,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_2"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_2"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.crm_team_1"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -183,7 +167,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_18"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_18"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.crm_team_1"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -202,7 +185,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_address_11"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_11"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -240,7 +222,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.crm_team_1"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -268,7 +249,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.crm_team_1"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -295,7 +275,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.crm_team_1"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -324,7 +303,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.crm_team_1"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -351,7 +329,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.crm_team_1"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -378,7 +355,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.crm_team_1"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -403,7 +379,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -430,7 +405,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -457,7 +431,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -484,7 +457,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -511,7 +483,6 @@
|
|||
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
|
|
@ -538,11 +509,9 @@
|
|||
<field name="partner_invoice_id" ref="base.partner_demo_portal"/>
|
||||
<field name="partner_shipping_id" ref="base.partner_demo_portal"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="state">sent</field>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
<field name="date_order" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
<field name="message_partner_ids" eval="[(4, ref('base.partner_demo_portal'))]"/>
|
||||
<field name="tag_ids" eval="[(4, ref('sales_team.categ_oppor4'))]"/>
|
||||
</record>
|
||||
|
||||
|
|
@ -572,10 +541,8 @@
|
|||
<field name="partner_invoice_id" ref="base.partner_demo_portal"/>
|
||||
<field name="partner_shipping_id" ref="base.partner_demo_portal"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
<field name="date_order" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
<field name="message_partner_ids" eval="[(4, ref('base.partner_demo_portal'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="portal_sale_order_line_4" model="sale.order.line">
|
||||
|
|
@ -683,7 +650,8 @@ Alright, thanks for the clarification. I will confirm the order as soon as I get
|
|||
<record id="sale_activity_3" model="mail.activity">
|
||||
<field name="res_id" ref="sale.sale_order_4"/>
|
||||
<field name="res_model_id" ref="sale.model_sale_order"/>
|
||||
<field name="activity_type_id" ref="sale.mail_act_sale_upsell"/>
|
||||
<field name="activity_type_id" ref="mail.mail_activity_data_todo"/>
|
||||
<field name="summary">Upsell</field>
|
||||
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=5)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
<field name="create_uid" ref="base.user_admin"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
|
|
@ -693,6 +661,7 @@ Alright, thanks for the clarification. I will confirm the order as soon as I get
|
|||
<field name="res_model_id" ref="sale.model_sale_order"/>
|
||||
<field name="activity_type_id" ref="mail.mail_activity_data_email"/>
|
||||
<field name="date_deadline" eval="DateTime.today().strftime('%Y-%m-%d %H:%M')"/>
|
||||
<field name="summary">Follow up on previous email</field>
|
||||
<field name="create_uid" ref="base.user_demo"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
</record>
|
||||
|
|
@ -719,13 +688,15 @@ Alright, thanks for the clarification. I will confirm the order as soon as I get
|
|||
<field name="res_model_id" ref="sale.model_sale_order"/>
|
||||
<field name="activity_type_id" ref="mail.mail_activity_data_email"/>
|
||||
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=5)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
<field name="summary">Confirm order via email</field>
|
||||
<field name="create_uid" ref="base.user_demo"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
</record>
|
||||
<record id="sale_activity_9" model="mail.activity">
|
||||
<field name="res_id" ref="sale.sale_order_16"/>
|
||||
<field name="res_model_id" ref="sale.model_sale_order"/>
|
||||
<field name="activity_type_id" ref="sale.mail_act_sale_upsell"/>
|
||||
<field name="activity_type_id" ref="mail.mail_activity_data_todo"/>
|
||||
<field name="summary">Upsell</field>
|
||||
<field name="date_deadline" eval="DateTime.today().strftime('%Y-%m-%d %H:%M')"/>
|
||||
<field name="create_uid" ref="base.user_demo"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
|
|
|
|||
10
odoo-bringout-oca-ocb-sale/sale/data/sale_tour.xml
Normal file
10
odoo-bringout-oca-ocb-sale/sale/data/sale_tour.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="sale_tour" model="web_tour.tour">
|
||||
<field name="name">sale_tour</field>
|
||||
<field name="sequence">20</field>
|
||||
<field name="rainbow_man_message"><![CDATA[
|
||||
<b>Congratulations</b>, your first quotation is sent!<br>Check your email to validate the quote.
|
||||
]]></field>
|
||||
</record>
|
||||
</odoo>
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
8419
odoo-bringout-oca-ocb-sale/sale/i18n/es_419.po
Normal file
8419
odoo-bringout-oca-ocb-sale/sale/i18n/es_419.po
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -6,9 +6,9 @@
|
|||
# Daniel Santibáñez Polanco <dansanti@gmail.com>, 2016
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 15.0\n"
|
||||
"Project-Id-Version: Odoo 9.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-05-23 08:23+0000\n"
|
||||
"POT-Creation-Date: 2024-02-07 10:23+0000\n"
|
||||
"PO-Revision-Date: 2016-03-13 01:19+0000\n"
|
||||
"Last-Translator: Daniel Santibáñez Polanco <dansanti@gmail.com>\n"
|
||||
"Language-Team: Spanish (Chile) (http://www.transifex.com/odoo/odoo-9/"
|
||||
|
|
@ -29,16 +29,6 @@ msgstr "# de líneas"
|
|||
msgid "<strong>Fiscal Position Remark:</strong>"
|
||||
msgstr "<strong>Posición fiscal:</strong>"
|
||||
|
||||
#. module: sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.report_saleorder_document
|
||||
msgid "<strong>Salesperson:</strong>"
|
||||
msgstr "<strong>Vendedor:</strong>"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__message_needaction
|
||||
msgid "Action Needed"
|
||||
msgstr "Acción necesaria"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__analytic_account_id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_report__analytic_account_id
|
||||
|
|
@ -67,6 +57,7 @@ msgstr "Compañías"
|
|||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_advance_payment_inv__company_id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__company_id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_discount__company_id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_line__company_id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_report__company_id
|
||||
#: model:ir.model.fields,field_description:sale.field_utm_campaign__company_id
|
||||
|
|
@ -77,14 +68,20 @@ msgstr "Compañía"
|
|||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_advance_payment_inv__advance_payment_method
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_form
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_sale_advance_payment_inv
|
||||
msgid "Create Invoice"
|
||||
msgstr "Crear factura"
|
||||
|
||||
#. module: sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.sale_order_tree
|
||||
msgid "Create Invoices"
|
||||
msgstr "Crear facturas"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_advance_payment_inv__create_uid
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_mass_cancel_orders__create_uid
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__create_uid
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_cancel__create_uid
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_discount__create_uid
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_line__create_uid
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_payment_provider_onboarding_wizard__create_uid
|
||||
msgid "Created by"
|
||||
|
|
@ -92,7 +89,9 @@ msgstr "Creado por"
|
|||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_advance_payment_inv__create_date
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_mass_cancel_orders__create_date
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_cancel__create_date
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_discount__create_date
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_line__create_date
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_payment_provider_onboarding_wizard__create_date
|
||||
msgid "Created on"
|
||||
|
|
@ -107,12 +106,16 @@ msgstr "Fecha creación"
|
|||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_advance_payment_inv__currency_id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__currency_id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_discount__currency_id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_line__currency_id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_report__currency_id
|
||||
#: model:ir.model.fields,field_description:sale.field_utm_campaign__currency_id
|
||||
msgid "Currency"
|
||||
msgstr "Moneda"
|
||||
|
||||
#. module: sale
|
||||
#. odoo-python
|
||||
#: code:addons/sale/models/sale_order.py:0
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__partner_id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_line__order_partner_id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_report__partner_id
|
||||
|
|
@ -126,11 +129,6 @@ msgstr "Cliente"
|
|||
msgid "Customer Reference"
|
||||
msgstr "Referencia cliente"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_advance_payment_inv__deposit_taxes_id
|
||||
msgid "Customer Taxes"
|
||||
msgstr "Impuestos de cliente"
|
||||
|
||||
#. module: sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_form
|
||||
msgid "Delivered"
|
||||
|
|
@ -150,41 +148,21 @@ msgstr "Descuento (%)"
|
|||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_advance_payment_inv__display_name
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_mass_cancel_orders__display_name
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__display_name
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_cancel__display_name
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_discount__display_name
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_line__display_name
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_payment_provider_onboarding_wizard__display_name
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_report__display_name
|
||||
msgid "Display Name"
|
||||
msgstr "Nombre mostrado"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields.selection,name:sale.selection__res_company__sale_onboarding_order_confirmation_state__done
|
||||
#: model:ir.model.fields.selection,name:sale.selection__res_company__sale_onboarding_sample_quotation_state__done
|
||||
#: model:ir.model.fields.selection,name:sale.selection__res_company__sale_quotation_onboarding_state__done
|
||||
msgid "Done"
|
||||
msgstr "Realizado"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model,name:sale.model_mail_compose_message
|
||||
msgid "Email composition wizard"
|
||||
msgstr "Asistente de redacción de correo electrónico."
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__fiscal_position_id
|
||||
msgid "Fiscal Position"
|
||||
msgstr "Posición fiscal"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__message_follower_ids
|
||||
msgid "Followers"
|
||||
msgstr "Seguidores"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__message_partner_ids
|
||||
msgid "Followers (Partners)"
|
||||
msgstr "Seguidores (Empresas)"
|
||||
|
||||
#. module: sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_product_search
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_sales_order_filter
|
||||
|
|
@ -194,24 +172,16 @@ msgstr "Agrupar por"
|
|||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_advance_payment_inv__id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_mass_cancel_orders__id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_cancel__id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_discount__id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_line__id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_payment_provider_onboarding_wizard__id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_report__id
|
||||
msgid "ID"
|
||||
msgstr "ID (identificación)"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,help:sale.field_sale_order__message_needaction
|
||||
msgid "If checked, new messages require your attention."
|
||||
msgstr "Si está marcado, hay nuevos mensajes que requieren su atención."
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_advance_payment_inv__deposit_account_id
|
||||
msgid "Income Account"
|
||||
msgstr "Cuenta de ingresos"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__partner_invoice_id
|
||||
msgid "Invoice Address"
|
||||
|
|
@ -225,6 +195,7 @@ msgstr "Líneas de factura"
|
|||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__invoice_status
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_line__invoice_status
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_report__invoice_status
|
||||
msgid "Invoice Status"
|
||||
msgstr "Estado de facturación"
|
||||
|
||||
|
|
@ -254,28 +225,16 @@ msgstr "Estadísticas de facturas"
|
|||
#. module: sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.crm_team_view_kanban_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:sale.res_config_settings_view_form
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_form
|
||||
msgid "Invoicing"
|
||||
msgstr "Facturando"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__message_is_follower
|
||||
msgid "Is Follower"
|
||||
msgstr "Es un seguidor"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_advance_payment_inv____last_update
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order____last_update
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_cancel____last_update
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_line____last_update
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_payment_provider_onboarding_wizard____last_update
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_report____last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr "Última modificación en"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_advance_payment_inv__write_uid
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_mass_cancel_orders__write_uid
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__write_uid
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_cancel__write_uid
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_discount__write_uid
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_line__write_uid
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_payment_provider_onboarding_wizard__write_uid
|
||||
msgid "Last Updated by"
|
||||
|
|
@ -283,18 +242,15 @@ msgstr "Última actualización de"
|
|||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_advance_payment_inv__write_date
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_mass_cancel_orders__write_date
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__write_date
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_cancel__write_date
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_discount__write_date
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_line__write_date
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_payment_provider_onboarding_wizard__write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr "Última actualización en"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__message_ids
|
||||
msgid "Messages"
|
||||
msgstr "Mensajes"
|
||||
|
||||
#. module: sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_sales_order_line_filter
|
||||
msgid "My Sales Order Lines"
|
||||
|
|
@ -303,15 +259,9 @@ msgstr "Mis lineas de pedidos de venta"
|
|||
#. module: sale
|
||||
#. odoo-python
|
||||
#: code:addons/sale/models/sale_order.py:0
|
||||
#, python-format
|
||||
msgid "New"
|
||||
msgstr "Nueva"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__message_needaction_counter
|
||||
msgid "Number of Actions"
|
||||
msgstr "Número de acciones"
|
||||
|
||||
#. module: sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_sales_order_filter
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_sales_order_line_filter
|
||||
|
|
@ -327,9 +277,7 @@ msgstr "Pedido"
|
|||
#: model_terms:ir.ui.view,arch_db:sale.sale_order_view_search_inherit_sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_form
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_product_search
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_tree
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_sales_order_filter
|
||||
#, python-format
|
||||
msgid "Order Date"
|
||||
msgstr "Fecha orden"
|
||||
|
||||
|
|
@ -357,15 +305,11 @@ msgstr "Estado del pedido"
|
|||
msgid "Pricelist"
|
||||
msgstr "Tarifa"
|
||||
|
||||
#. module: sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.sale_order_portal_template
|
||||
msgid "Print"
|
||||
msgstr "Imprimir"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model,name:sale.model_product_template
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_line__product_id
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_report__product_tmpl_id
|
||||
#: model_terms:ir.ui.view,arch_db:sale.sale_report_view_tree
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_form
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_product_search
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_sales_order_filter
|
||||
|
|
@ -387,6 +331,7 @@ msgstr "Plantilla producto"
|
|||
#. module: sale
|
||||
#: model:ir.actions.act_window,name:sale.product_template_action
|
||||
#: model:ir.ui.menu,name:sale.menu_product_template_action
|
||||
#: model:ir.ui.menu,name:sale.menu_reporting_product
|
||||
#: model:ir.ui.menu,name:sale.prod_config_main
|
||||
#: model:ir.ui.menu,name:sale.product_menu_catalog
|
||||
#: model_terms:ir.ui.view,arch_db:sale.sale_order_portal_content
|
||||
|
|
@ -408,32 +353,27 @@ msgstr "Cantidad"
|
|||
#. module: sale
|
||||
#. odoo-python
|
||||
#: code:addons/sale/models/sale_order.py:0
|
||||
#: model:ir.model.fields.selection,name:sale.selection__product_document__attached_on__quotation
|
||||
#: model:ir.model.fields.selection,name:sale.selection__sale_order__state__draft
|
||||
#: model:ir.model.fields.selection,name:sale.selection__sale_report__state__draft
|
||||
#: model_terms:ir.ui.view,arch_db:sale.crm_team_view_kanban_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_quotation_tree
|
||||
#, python-format
|
||||
#: model_terms:ir.ui.view,arch_db:sale.product_document_search
|
||||
msgid "Quotation"
|
||||
msgstr "Presupuesto"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.actions.report,name:sale.action_report_saleorder
|
||||
msgid "Quotation / Order"
|
||||
msgstr "Presupuesto / Pedido"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.actions.act_window,name:sale.action_quotations
|
||||
#: model:ir.actions.act_window,name:sale.action_quotations_salesteams
|
||||
#: model:ir.actions.act_window,name:sale.action_quotations_with_onboarding
|
||||
#: model:ir.model.fields,field_description:sale.field_crm_team__use_quotations
|
||||
#: model:ir.ui.menu,name:sale.menu_sale_quotations
|
||||
#: model_terms:ir.ui.view,arch_db:sale.crm_team_view_kanban_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:sale.portal_my_home_menu_sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.portal_my_home_sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.portal_my_quotations
|
||||
#: model_terms:ir.ui.view,arch_db:sale.sale_order_view_search_inherit_quotation
|
||||
#: model_terms:ir.ui.view,arch_db:sale.utm_campaign_view_form
|
||||
#: model_terms:ir.ui.view,arch_db:sale.utm_campaign_view_kanban
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_product_search
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_quotation_tree
|
||||
msgid "Quotations"
|
||||
msgstr "Presupuestos"
|
||||
|
||||
|
|
@ -449,8 +389,10 @@ msgstr "Informes"
|
|||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_product_packaging__sales
|
||||
#: model:ir.ui.menu,name:sale.menu_reporting_sales
|
||||
#: model:ir.ui.menu,name:sale.sale_menu_root
|
||||
#: model_terms:ir.ui.view,arch_db:sale.crm_team_view_kanban_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:sale.product_document_form
|
||||
#: model_terms:ir.ui.view,arch_db:sale.res_config_settings_view_form
|
||||
#: model_terms:ir.ui.view,arch_db:sale.res_partner_view_buttons
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_form
|
||||
|
|
@ -472,7 +414,6 @@ msgstr "Ventas. Anticipo pago factura"
|
|||
#: model_terms:ir.ui.view,arch_db:sale.view_order_product_graph
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_product_pivot
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_product_search
|
||||
#, python-format
|
||||
msgid "Sales Analysis"
|
||||
msgstr "Análisis de ventas"
|
||||
|
||||
|
|
@ -483,9 +424,11 @@ msgstr "Análisis de ventas"
|
|||
#: model:ir.model.fields,field_description:sale.field_res_partner__sale_order_ids
|
||||
#: model:ir.model.fields,field_description:sale.field_res_users__sale_order_ids
|
||||
#: model:ir.model.fields.selection,name:sale.selection__sale_order__state__sale
|
||||
#: model:ir.model.fields.selection,name:sale.selection__sale_report__order_reference__sale_order
|
||||
#: model:ir.model.fields.selection,name:sale.selection__sale_report__state__sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.product_document_search
|
||||
#: model_terms:ir.ui.view,arch_db:sale.sale_order_view_activity
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_form
|
||||
#, python-format
|
||||
msgid "Sales Order"
|
||||
msgstr "Pedido de venta"
|
||||
|
||||
|
|
@ -513,8 +456,7 @@ msgstr "Líneas pedido de ventas"
|
|||
#: model_terms:ir.ui.view,arch_db:sale.crm_team_view_kanban_dashboard
|
||||
#: model_terms:ir.ui.view,arch_db:sale.portal_my_home_menu_sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.portal_my_home_sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.portal_my_orders
|
||||
#: model_terms:ir.ui.view,arch_db:sale.sale_order_view_activity
|
||||
#: model_terms:ir.ui.view,arch_db:sale.sale_order_tree
|
||||
#: model_terms:ir.ui.view,arch_db:sale.sale_order_view_search_inherit_quotation
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_product_search
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_tree
|
||||
|
|
@ -586,6 +528,9 @@ msgstr "Impuestos"
|
|||
|
||||
#. module: sale
|
||||
#: model_terms:ir.actions.act_window,help:sale.action_order_report_all
|
||||
#: model_terms:ir.actions.act_window,help:sale.action_order_report_customers
|
||||
#: model_terms:ir.actions.act_window,help:sale.action_order_report_products
|
||||
#: model_terms:ir.actions.act_window,help:sale.action_order_report_salesperson
|
||||
msgid ""
|
||||
"This report performs analysis on your quotations and sales orders. Analysis "
|
||||
"check your sales revenues and sort it by different group criteria (salesman, "
|
||||
|
|
@ -603,8 +548,10 @@ msgstr ""
|
|||
#. module: sale
|
||||
#: model:ir.model.fields.selection,name:sale.selection__sale_order__invoice_status__to_invoice
|
||||
#: model:ir.model.fields.selection,name:sale.selection__sale_order_line__invoice_status__to_invoice
|
||||
#: model:ir.model.fields.selection,name:sale.selection__sale_report__invoice_status__to_invoice
|
||||
#: model:ir.ui.menu,name:sale.menu_sale_invoicing
|
||||
#: model_terms:ir.ui.view,arch_db:sale.sale_order_view_search_inherit_sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_product_search
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_sales_order_line_filter
|
||||
msgid "To Invoice"
|
||||
msgstr "Para facturar"
|
||||
|
|
@ -620,8 +567,7 @@ msgid "Total"
|
|||
msgstr "Total"
|
||||
|
||||
#. module: sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_order_tree
|
||||
#: model_terms:ir.ui.view,arch_db:sale.view_quotation_tree
|
||||
#: model_terms:ir.ui.view,arch_db:sale.sale_order_tree
|
||||
msgid "Total Tax Included"
|
||||
msgstr "Total impuestos incluidos"
|
||||
|
||||
|
|
@ -642,28 +588,18 @@ msgstr "Unidad de medida"
|
|||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__amount_untaxed
|
||||
msgid "Untaxed Amount"
|
||||
msgstr "Total neto"
|
||||
msgstr "Monto neto"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_report__untaxed_amount_invoiced
|
||||
msgid "Untaxed Amount Invoiced"
|
||||
msgstr "Total neto facturado"
|
||||
msgstr "Monto neto facturado"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order_line__untaxed_amount_to_invoice
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_report__untaxed_amount_to_invoice
|
||||
msgid "Untaxed Amount To Invoice"
|
||||
msgstr "Total neto por facturar"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,field_description:sale.field_sale_order__website_message_ids
|
||||
msgid "Website Messages"
|
||||
msgstr "Mensajes del sitio web"
|
||||
|
||||
#. module: sale
|
||||
#: model:ir.model.fields,help:sale.field_sale_order__website_message_ids
|
||||
msgid "Website communication history"
|
||||
msgstr "Historial de comunicaciones del sitio web"
|
||||
msgstr "Monto neto por facturar"
|
||||
|
||||
#. module: sale
|
||||
#: model_terms:ir.ui.view,arch_db:sale.res_config_settings_view_form
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
6874
odoo-bringout-oca-ocb-sale/sale/i18n/uz.po
Normal file
6874
odoo-bringout-oca-ocb-sale/sale/i18n/uz.po
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,16 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import analytic
|
||||
from . import account_move
|
||||
from . import account_move_line
|
||||
from . import chart_template
|
||||
from . import crm_team
|
||||
from . import ir_actions_report
|
||||
from . import ir_config_parameter
|
||||
from . import payment_provider
|
||||
from . import payment_transaction
|
||||
from . import product_document
|
||||
from . import product_pricelist_item
|
||||
from . import product_product
|
||||
from . import product_template
|
||||
from . import res_company
|
||||
from . import res_config_settings
|
||||
from . import res_partner
|
||||
from . import sale_order
|
||||
from . import sale_order_line
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue