[MERGE] forward port branch saas-12 up to 9f28139
This commit is contained in:
@@ -283,7 +283,7 @@
|
||||
<field name="code"/>
|
||||
<field name="refund_sequence" attrs="{'invisible': [('type', 'not in', ['sale', 'purchase'])]}" groups="base.group_no_one"/>
|
||||
<field name="sequence_id" required="0" attrs="{'readonly': 1}" groups="base.group_no_one"/>
|
||||
<field name="refund_sequence_id" attrs="{'required': [('refund_sequence', '=', True)], 'invisible': ['|',('type', 'not in', ['sale', 'purchase']), ('refund_sequence', '!=', True)]}" required="0" groups="base.group_no_one"/>
|
||||
<field name="refund_sequence_id" attrs="{'invisible': ['|',('type', 'not in', ['sale', 'purchase']), ('refund_sequence', '!=', True)], 'required': [('type', 'in', ['sale', 'purchase']), ('refund_sequence', '=', True)]}" groups="base.group_no_one"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="default_debit_account_id" domain="[('deprecated', '=', False)]" />
|
||||
|
||||
@@ -455,7 +455,7 @@ class AccountAssetDepreciationLine(models.Model):
|
||||
current_currency = line.asset_id.currency_id
|
||||
amount = current_currency.compute(line.amount, company_currency)
|
||||
sign = (category_id.journal_id.type == 'purchase' or category_id.journal_id.type == 'sale' and 1) or -1
|
||||
asset_name = line.asset_id.name + ' (%s/%s)' % (line.sequence, line.asset_id.method_number)
|
||||
asset_name = line.asset_id.name + ' (%s/%s)' % (line.sequence, len(line.asset_id.depreciation_line_ids))
|
||||
prec = self.env['decimal.precision'].precision_get('Account')
|
||||
move_line_1 = {
|
||||
'name': asset_name,
|
||||
|
||||
@@ -51,15 +51,15 @@ class AccountInvoiceLine(models.Model):
|
||||
|
||||
@api.one
|
||||
def asset_create(self):
|
||||
if self.asset_category_id and self.asset_category_id.method_number > 1:
|
||||
if self.asset_category_id:
|
||||
vals = {
|
||||
'name': self.name,
|
||||
'code': self.invoice_id.number or False,
|
||||
'category_id': self.asset_category_id.id,
|
||||
'value': self.price_subtotal,
|
||||
'value': self.price_subtotal_signed,
|
||||
'partner_id': self.invoice_id.partner_id.id,
|
||||
'company_id': self.invoice_id.company_id.id,
|
||||
'currency_id': self.invoice_id.currency_id.id,
|
||||
'currency_id': self.invoice_id.company_currency_id.id,
|
||||
'date': self.asset_start_date or self.invoice_id.date_invoice,
|
||||
'invoice_id': self.invoice_id.id,
|
||||
}
|
||||
|
||||
@@ -8,19 +8,22 @@ from odoo.exceptions import UserError
|
||||
class AccountPartialReconcileCashBasis(models.Model):
|
||||
_inherit = 'account.partial.reconcile'
|
||||
|
||||
def create_tax_cash_basis_entry(self, value_before_reconciliation):
|
||||
def _get_tax_cash_basis_lines(self, value_before_reconciliation):
|
||||
# Search in account_move if we have any taxes account move lines
|
||||
tax_group = {}
|
||||
total_by_cash_basis_account = {}
|
||||
line_to_create = []
|
||||
move_date = self.debit_move_id.date
|
||||
for move in (self.debit_move_id.move_id, self.credit_move_id.move_id):
|
||||
if move_date < move.date:
|
||||
move_date = move.date
|
||||
for line in move.line_ids:
|
||||
#TOCHECK: normal and cash basis taxes shoudn't be mixed together (on the same invoice line for example) as it will
|
||||
#create reporting issues. Not sure of the behavior to implement in that case, though.
|
||||
# amount to write is the current cash_basis amount minus the one before the reconciliation
|
||||
matched_percentage = value_before_reconciliation[move.id]
|
||||
amount = (line.credit_cash_basis - line.debit_cash_basis) - (line.credit - line.debit) * matched_percentage
|
||||
if not line.tax_exigible:
|
||||
# amount to write is the current cash_basis amount minus the one before the reconciliation
|
||||
matched_percentage = value_before_reconciliation[move.id]
|
||||
amount = (line.credit_cash_basis - line.debit_cash_basis) - (line.credit - line.debit) * matched_percentage
|
||||
if line.tax_line_id and line.tax_line_id.use_cash_basis:
|
||||
# group by line account
|
||||
acc = line.account_id.id
|
||||
@@ -28,7 +31,6 @@ class AccountPartialReconcileCashBasis(models.Model):
|
||||
tax_group[acc] += amount
|
||||
else:
|
||||
tax_group[acc] = amount
|
||||
|
||||
# Group by cash basis account and tax
|
||||
acc = line.tax_line_id.cash_basis_account.id
|
||||
key = (acc, line.tax_line_id.id)
|
||||
@@ -36,8 +38,8 @@ class AccountPartialReconcileCashBasis(models.Model):
|
||||
total_by_cash_basis_account[key] += amount
|
||||
else:
|
||||
total_by_cash_basis_account[key] = amount
|
||||
for tax in line.tax_ids:
|
||||
if tax.use_cash_basis:
|
||||
if any([tax.use_cash_basis for tax in line.tax_ids]):
|
||||
for tax in line.tax_ids:
|
||||
line_to_create.append((0, 0, {
|
||||
'name': '/',
|
||||
'debit': line.debit_cash_basis - line.debit * matched_percentage,
|
||||
@@ -45,13 +47,14 @@ class AccountPartialReconcileCashBasis(models.Model):
|
||||
'account_id': line.account_id.id,
|
||||
'tax_ids': [(6, 0, [tax.id])],
|
||||
'tax_exigible': True,
|
||||
}))
|
||||
}))
|
||||
line_to_create.append((0, 0, {
|
||||
'name': '/',
|
||||
'credit': line.debit_cash_basis - line.debit * matched_percentage,
|
||||
'debit': line.credit_cash_basis - line.credit * matched_percentage,
|
||||
'account_id': line.account_id.id,
|
||||
}))
|
||||
'tax_exigible': True,
|
||||
}))
|
||||
|
||||
for k, v in tax_group.items():
|
||||
line_to_create.append((0, 0, {
|
||||
@@ -59,7 +62,8 @@ class AccountPartialReconcileCashBasis(models.Model):
|
||||
'debit': v if v > 0 else 0.0,
|
||||
'credit': abs(v) if v < 0 else 0.0,
|
||||
'account_id': k,
|
||||
}))
|
||||
'tax_exigible': True,
|
||||
}))
|
||||
|
||||
# Create counterpart vals
|
||||
for key, v in total_by_cash_basis_account.items():
|
||||
@@ -71,19 +75,29 @@ class AccountPartialReconcileCashBasis(models.Model):
|
||||
'account_id': k,
|
||||
'tax_line_id': tax_id,
|
||||
'tax_exigible': True,
|
||||
}))
|
||||
}))
|
||||
return line_to_create, move_date
|
||||
|
||||
# Create move
|
||||
def create_tax_cash_basis_entry(self, value_before_reconciliation):
|
||||
line_to_create, move_date = self._get_tax_cash_basis_lines(value_before_reconciliation)
|
||||
if len(line_to_create) > 0:
|
||||
# Check if company_journal for cash basis is set if not, raise exception
|
||||
if not self.company_id.tax_cash_basis_journal_id:
|
||||
raise UserError(_('There is no tax cash basis journal defined ' \
|
||||
'for this company: "%s" \nConfigure it in Accounting/Configuration/Settings') % \
|
||||
(self.company_id.name))
|
||||
move = self.env['account.move'].with_context(dont_create_taxes=True).create({
|
||||
raise UserError(_('There is no tax cash basis journal defined '
|
||||
'for this company: "%s" \nConfigure it in Accounting/Configuration/Settings') %
|
||||
(self.company_id.name))
|
||||
move_vals = {
|
||||
'journal_id': self.company_id.tax_cash_basis_journal_id.id,
|
||||
'line_ids': line_to_create,
|
||||
'tax_cash_basis_rec_id': self.id})
|
||||
'tax_cash_basis_rec_id': self.id
|
||||
}
|
||||
# The move date should be the maximum date between payment and invoice (in case
|
||||
# of payment in advance). However, we should make sure the move date is not
|
||||
# recorded after the period lock date as the tax statement for this period is
|
||||
# probably already sent to the estate.
|
||||
if move_date > self.company_id.period_lock_date:
|
||||
move_vals['date'] = move_date
|
||||
move = self.env['account.move'].with_context(dont_create_taxes=True).create(move_vals)
|
||||
# post move
|
||||
move.post()
|
||||
|
||||
|
||||
@@ -17,18 +17,18 @@ class HrExpense(models.Model):
|
||||
_description = "Expense"
|
||||
_order = "date desc, id desc"
|
||||
|
||||
name = fields.Char(string='Expense Description', readonly=True, required=True, states={'draft': [('readonly', False)]})
|
||||
date = fields.Date(readonly=True, states={'draft': [('readonly', False)]}, default=fields.Date.context_today, string="Date")
|
||||
employee_id = fields.Many2one('hr.employee', string="Employee", required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1))
|
||||
product_id = fields.Many2one('product.product', string='Product', readonly=True, states={'draft': [('readonly', False)]}, domain=[('can_be_expensed', '=', True)], required=True)
|
||||
product_uom_id = fields.Many2one('product.uom', string='Unit of Measure', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env['product.uom'].search([], limit=1, order='id'))
|
||||
unit_amount = fields.Float(string='Unit Price', readonly=True, required=True, states={'draft': [('readonly', False)]}, digits=dp.get_precision('Product Price'))
|
||||
quantity = fields.Float(required=True, readonly=True, states={'draft': [('readonly', False)]}, digits=dp.get_precision('Product Unit of Measure'), default=1)
|
||||
name = fields.Char(string='Expense Description', readonly=True, required=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]})
|
||||
date = fields.Date(readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=fields.Date.context_today, string="Date")
|
||||
employee_id = fields.Many2one('hr.employee', string="Employee", required=True, readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1))
|
||||
product_id = fields.Many2one('product.product', string='Product', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, domain=[('can_be_expensed', '=', True)], required=True)
|
||||
product_uom_id = fields.Many2one('product.uom', string='Unit of Measure', required=True, readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env['product.uom'].search([], limit=1, order='id'))
|
||||
unit_amount = fields.Float(string='Unit Price', readonly=True, required=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, digits=dp.get_precision('Product Price'))
|
||||
quantity = fields.Float(required=True, readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, digits=dp.get_precision('Product Unit of Measure'), default=1)
|
||||
tax_ids = fields.Many2many('account.tax', 'expense_tax', 'expense_id', 'tax_id', string='Taxes', states={'done': [('readonly', True)], 'post': [('readonly', True)]})
|
||||
untaxed_amount = fields.Float(string='Subtotal', store=True, compute='_compute_amount', digits=dp.get_precision('Account'))
|
||||
total_amount = fields.Float(string='Total', store=True, compute='_compute_amount', digits=dp.get_precision('Account'))
|
||||
company_id = fields.Many2one('res.company', string='Company', readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env.user.company_id)
|
||||
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env.user.company_id.currency_id)
|
||||
company_id = fields.Many2one('res.company', string='Company', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.user.company_id)
|
||||
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.user.company_id.currency_id)
|
||||
analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, oldname='analytic_account')
|
||||
account_id = fields.Many2one('account.account', string='Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, default=lambda self: self.env['ir.property'].get('property_account_expense_categ_id', 'product.category'))
|
||||
description = fields.Text()
|
||||
|
||||
@@ -55,7 +55,7 @@ class Task(models.Model):
|
||||
|
||||
parent_id = fields.Many2one('project.task', string='Parent Task')
|
||||
child_ids = fields.One2many('project.task', 'parent_id', string="Sub-tasks")
|
||||
subtask_project_id = fields.Many2one('project.project', related="project_id.subtask_project_id", string='Sub-task Project')
|
||||
subtask_project_id = fields.Many2one('project.project', related="project_id.subtask_project_id", string='Sub-task Project', readonly=True)
|
||||
subtask_count = fields.Integer(compute='_get_subtask_count', type='integer', string="Sub-task count")
|
||||
|
||||
_constraints = [(models.BaseModel._check_recursion, 'Circular references are not permitted between tasks and sub-tasks', ['parent_id'])]
|
||||
|
||||
@@ -10,6 +10,7 @@ from odoo.http import request
|
||||
class LinkTracker(http.Controller):
|
||||
@http.route('/r/<string:code>', type='http', auth='none', website=True)
|
||||
def full_url_redirect(self, code, **post):
|
||||
request.env['link.tracker.click'].add_click(code, request.httprequest.remote_addr, request.session['geoip'].get('country_code'), stat_id=False)
|
||||
country_code = request.session.geoip and request.session.geoip.get('country_code') or False
|
||||
request.env['link.tracker.click'].add_click(code, request.httprequest.remote_addr, country_code, stat_id=False)
|
||||
redirect_url = request.env['link.tracker'].get_url_from_code(code)
|
||||
return werkzeug.utils.redirect(redirect_url or '', 301)
|
||||
|
||||
@@ -672,7 +672,7 @@ var chat_manager = {
|
||||
// post a message in a channel or execute a command
|
||||
return ChannelModel.call(data.command ? 'execute_command' : 'message_post', [options.channel_id], _.extend(msg, {
|
||||
message_type: 'comment',
|
||||
content_subtype: 'plaintext',
|
||||
content_subtype: 'html',
|
||||
subtype: 'mail.mt_comment',
|
||||
command: data.command,
|
||||
}));
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<field name="open"/>
|
||||
<field name="memo"/>
|
||||
<field name="date_done"/>
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="message_partner_ids"/>
|
||||
<field name="tag_ids"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
@@ -101,9 +101,9 @@
|
||||
<!-- title -->
|
||||
<field name="name"/>
|
||||
</span>
|
||||
<t t-if="record.message_follower_ids.raw_value.length > 1">
|
||||
<t t-if="record.message_partner_ids.raw_value.length > 1">
|
||||
<div class="clearfix"></div>
|
||||
<t t-foreach="record.message_follower_ids.raw_value" t-as="follower">
|
||||
<t t-foreach="record.message_partner_ids.raw_value" t-as="follower">
|
||||
<img t-att-src="kanban_image('res.partner', 'image_small', follower)" width="24" height="24" class="oe_kanban_avatar pull-right" t-att-data-member_id="follower"/>
|
||||
</t>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
@@ -20,9 +20,9 @@ class AcquirerAdyen(models.Model):
|
||||
_inherit = 'payment.acquirer'
|
||||
|
||||
provider = fields.Selection(selection_add=[('adyen', 'Adyen')])
|
||||
adyen_merchant_account = fields.Char('Merchant Account', required_if_provider='adyen')
|
||||
adyen_skin_code = fields.Char('Skin Code', required_if_provider='adyen')
|
||||
adyen_skin_hmac_key = fields.Char('Skin HMAC Key', required_if_provider='adyen')
|
||||
adyen_merchant_account = fields.Char('Merchant Account', required_if_provider='adyen', groups='base.group_user')
|
||||
adyen_skin_code = fields.Char('Skin Code', required_if_provider='adyen', groups='base.group_user')
|
||||
adyen_skin_hmac_key = fields.Char('Skin HMAC Key', required_if_provider='adyen', groups='base.group_user')
|
||||
|
||||
@api.model
|
||||
def _get_adyen_urls(self, environment):
|
||||
|
||||
@@ -21,8 +21,8 @@ class PaymentAcquirerAuthorize(models.Model):
|
||||
_inherit = 'payment.acquirer'
|
||||
|
||||
provider = fields.Selection(selection_add=[('authorize', 'Authorize.Net')])
|
||||
authorize_login = fields.Char(string='API Login Id', required_if_provider='authorize')
|
||||
authorize_transaction_key = fields.Char(string='API Transaction Key', required_if_provider='authorize')
|
||||
authorize_login = fields.Char(string='API Login Id', required_if_provider='authorize', groups='base.group_user')
|
||||
authorize_transaction_key = fields.Char(string='API Transaction Key', required_if_provider='authorize', groups='base.group_user')
|
||||
|
||||
def _get_feature_support(self):
|
||||
"""Get advanced feature support by provider.
|
||||
|
||||
@@ -26,8 +26,8 @@ class AcquirerBuckaroo(models.Model):
|
||||
_inherit = 'payment.acquirer'
|
||||
|
||||
provider = fields.Selection(selection_add=[('buckaroo', 'Buckaroo')])
|
||||
brq_websitekey = fields.Char('WebsiteKey', required_if_provider='buckaroo')
|
||||
brq_secretkey = fields.Char('SecretKey', required_if_provider='buckaroo')
|
||||
brq_websitekey = fields.Char('WebsiteKey', required_if_provider='buckaroo', groups='base.group_user')
|
||||
brq_secretkey = fields.Char('SecretKey', required_if_provider='buckaroo', groups='base.group_user')
|
||||
|
||||
def _get_buckaroo_urls(self, environment):
|
||||
""" Buckaroo URLs
|
||||
|
||||
@@ -27,11 +27,11 @@ class PaymentAcquirerOgone(models.Model):
|
||||
_inherit = 'payment.acquirer'
|
||||
|
||||
provider = fields.Selection(selection_add=[('ogone', 'Ogone')])
|
||||
ogone_pspid = fields.Char('PSPID', required_if_provider='ogone')
|
||||
ogone_userid = fields.Char('API User ID', required_if_provider='ogone')
|
||||
ogone_password = fields.Char('API User Password', required_if_provider='ogone')
|
||||
ogone_shakey_in = fields.Char('SHA Key IN', size=32, required_if_provider='ogone')
|
||||
ogone_shakey_out = fields.Char('SHA Key OUT', size=32, required_if_provider='ogone')
|
||||
ogone_pspid = fields.Char('PSPID', required_if_provider='ogone', groups='base.group_user')
|
||||
ogone_userid = fields.Char('API User ID', required_if_provider='ogone', groups='base.group_user')
|
||||
ogone_password = fields.Char('API User Password', required_if_provider='ogone', groups='base.group_user')
|
||||
ogone_shakey_in = fields.Char('SHA Key IN', size=32, required_if_provider='ogone', groups='base.group_user')
|
||||
ogone_shakey_out = fields.Char('SHA Key OUT', size=32, required_if_provider='ogone', groups='base.group_user')
|
||||
ogone_alias_usage = fields.Char('Alias Usage', default="Allow saving my payment data",
|
||||
help="If you want to use Ogone Aliases, this default "
|
||||
"Alias Usage will be presented to the customer as the "
|
||||
|
||||
@@ -17,17 +17,17 @@ class AcquirerPaypal(models.Model):
|
||||
_inherit = 'payment.acquirer'
|
||||
|
||||
provider = fields.Selection(selection_add=[('paypal', 'Paypal')])
|
||||
paypal_email_account = fields.Char('Paypal Email ID', required_if_provider='paypal')
|
||||
paypal_email_account = fields.Char('Paypal Email ID', required_if_provider='paypal', groups='base.group_user')
|
||||
paypal_seller_account = fields.Char(
|
||||
'Paypal Merchant ID',
|
||||
'Paypal Merchant ID', groups='base.group_user',
|
||||
help='The Merchant ID is used to ensure communications coming from Paypal are valid and secured.')
|
||||
paypal_use_ipn = fields.Boolean('Use IPN', default=True, help='Paypal Instant Payment Notification')
|
||||
paypal_use_ipn = fields.Boolean('Use IPN', default=True, help='Paypal Instant Payment Notification', groups='base.group_user')
|
||||
# Server 2 server
|
||||
paypal_api_enabled = fields.Boolean('Use Rest API', default=False)
|
||||
paypal_api_username = fields.Char('Rest API Username')
|
||||
paypal_api_password = fields.Char('Rest API Password')
|
||||
paypal_api_access_token = fields.Char('Access Token')
|
||||
paypal_api_access_token_validity = fields.Datetime('Access Token Validity')
|
||||
paypal_api_username = fields.Char('Rest API Username', groups='base.group_user')
|
||||
paypal_api_password = fields.Char('Rest API Password', groups='base.group_user')
|
||||
paypal_api_access_token = fields.Char('Access Token', groups='base.group_user')
|
||||
paypal_api_access_token_validity = fields.Datetime('Access Token Validity', groups='base.group_user')
|
||||
# Default paypal fees
|
||||
fees_dom_fixed = fields.Float(default=0.35)
|
||||
fees_dom_var = fields.Float(default=3.4)
|
||||
|
||||
@@ -26,7 +26,7 @@ class SipsController(http.Controller):
|
||||
|
||||
def sips_validate_data(self, **post):
|
||||
sips = request.env['payment.acquirer'].search([('provider', '=', 'sips')], limit=1)
|
||||
security = sips._sips_generate_shasign(post)
|
||||
security = sips.sudo()._sips_generate_shasign(post)
|
||||
if security == post['Seal']:
|
||||
_logger.debug('Sips: validated data')
|
||||
return request.env['payment.transaction'].sudo().form_feedback(post, 'sips')
|
||||
|
||||
@@ -37,8 +37,8 @@ class AcquirerSips(models.Model):
|
||||
_inherit = 'payment.acquirer'
|
||||
|
||||
provider = fields.Selection(selection_add=[('sips', 'Sips')])
|
||||
sips_merchant_id = fields.Char('SIPS API User Password', required_if_provider='sips')
|
||||
sips_secret = fields.Char('SIPS Secret', size=64, required_if_provider='sips')
|
||||
sips_merchant_id = fields.Char('SIPS API User Password', required_if_provider='sips', groups='base.group_user')
|
||||
sips_secret = fields.Char('SIPS Secret', size=64, required_if_provider='sips', groups='base.group_user')
|
||||
|
||||
def _get_sips_urls(self, environment):
|
||||
""" Worldline SIPS URLS """
|
||||
|
||||
@@ -57,6 +57,7 @@ Main Features
|
||||
'views/res_config_view.xml',
|
||||
'views/report_statement.xml',
|
||||
'views/report_userlabel.xml',
|
||||
'views/report_saledetails.xml',
|
||||
'views/point_of_sale.xml',
|
||||
'views/point_of_sale_dashboard.xml',
|
||||
],
|
||||
|
||||
@@ -22,3 +22,10 @@ class PosController(http.Controller):
|
||||
'session_info': json.dumps(request.env['ir.http'].session_info())
|
||||
}
|
||||
return request.render('point_of_sale.index', qcontext=context)
|
||||
|
||||
@http.route('/pos/sale_details_report', type='http', auth='user')
|
||||
def print_sale_details(self, date_start=False, date_stop=False, **kw):
|
||||
r = request.env['report.point_of_sale.report_saledetails']
|
||||
pdf = request.env['report'].with_context(date_start=date_start, date_stop=date_stop).get_pdf(r, 'point_of_sale.report_saledetails')
|
||||
pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))]
|
||||
return request.make_response(pdf, headers=pdfhttpheaders)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from functools import partial
|
||||
|
||||
import psycopg2
|
||||
@@ -8,7 +9,7 @@ import psycopg2
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.tools import float_is_zero
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
from odoo.http import request
|
||||
import odoo.addons.decimal_precision as dp
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -781,3 +782,104 @@ class PosOrderLineLot(models.Model):
|
||||
order_id = fields.Many2one('pos.order', related="pos_order_line_id.order_id")
|
||||
lot_name = fields.Char('Lot Name')
|
||||
product_id = fields.Many2one('product.product', related='pos_order_line_id.product_id')
|
||||
|
||||
|
||||
class ReportSaleDetails(models.AbstractModel):
|
||||
|
||||
_name = 'report.point_of_sale.report_saledetails'
|
||||
|
||||
|
||||
@api.model
|
||||
def get_sale_details(self, date_start=False, date_stop=False, company=False):
|
||||
""" Serialise the orders of the day information
|
||||
|
||||
params: date_start, date_stop string representing the day of order
|
||||
"""
|
||||
if not company:
|
||||
company = self.env.user.company_id
|
||||
|
||||
today = fields.Datetime.from_string(fields.Date.context_today(self))
|
||||
if date_start:
|
||||
date_start = fields.Datetime.from_string(date_start)
|
||||
else:
|
||||
# start by default today 00:00:00
|
||||
date_start = today
|
||||
|
||||
if date_stop:
|
||||
# set time to 23:59:59
|
||||
date_stop = fields.Datetime.from_string(date_stop) + timedelta(days=1, seconds=-1)
|
||||
else:
|
||||
# stop by default today 23:59:59
|
||||
date_stop = today + timedelta(days=1, seconds=-1)
|
||||
|
||||
# avoid a date_stop smaller than date_start
|
||||
date_stop = max(date_stop, date_start)
|
||||
|
||||
date_start = fields.Datetime.to_string(date_start)
|
||||
date_stop = fields.Datetime.to_string(date_stop)
|
||||
orders = self.env['pos.order'].search([('date_order', '>=', date_start), ('date_order', '<=', date_stop),
|
||||
('state', 'in', ['paid','invoiced','done']),
|
||||
('company_id', '=', company.id)])
|
||||
user_currency = self.env.user.company_id.currency_id
|
||||
|
||||
total = 0.0
|
||||
products_sold = {}
|
||||
taxes = {}
|
||||
for order in orders:
|
||||
if user_currency != order.pricelist_id.currency_id:
|
||||
total += order.pricelist_id.currency_id.compute(order.amount_total, user_currency)
|
||||
else:
|
||||
total += order.amount_total
|
||||
currency = order.session_id.currency_id
|
||||
|
||||
for line in order.lines:
|
||||
key = (line.product_id, line.price_unit, line.discount)
|
||||
products_sold.setdefault(key, 0.0)
|
||||
products_sold[key] += line.qty
|
||||
|
||||
if line.tax_ids_after_fiscal_position:
|
||||
line_taxes = line.tax_ids_after_fiscal_position.compute_all(line.price_unit * (1-(line.discount or 0.0)/100.0), currency, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)
|
||||
for tax in line_taxes['taxes']:
|
||||
taxes.setdefault(tax['id'], {'name': tax['name'], 'total':0.0})
|
||||
taxes[tax['id']]['total'] += tax['amount']
|
||||
|
||||
st_line_ids = self.env["account.bank.statement.line"].search([('pos_statement_id', 'in', orders.ids)]).ids
|
||||
if st_line_ids:
|
||||
self.env.cr.execute("""
|
||||
SELECT aj.name, sum(amount) total
|
||||
FROM account_bank_statement_line AS absl,
|
||||
account_bank_statement AS abs,
|
||||
account_journal AS aj
|
||||
WHERE absl.statement_id = abs.id
|
||||
AND abs.journal_id = aj.id
|
||||
AND absl.id IN %s
|
||||
GROUP BY aj.name
|
||||
""", (tuple(st_line_ids),))
|
||||
payments = self.env.cr.dictfetchall()
|
||||
else:
|
||||
payments = []
|
||||
|
||||
return {
|
||||
'total_paid': user_currency.round(total),
|
||||
'payments': payments,
|
||||
'company_name': self.env.user.company_id.name,
|
||||
'taxes': taxes.values(),
|
||||
'products': [{
|
||||
'product_id': product.id,
|
||||
'product_name': product.name[:20],
|
||||
'code': product.default_code,
|
||||
'quantity': qty,
|
||||
'price_unit': price_unit,
|
||||
'discount': discount,
|
||||
'uom': product.uom_id.name
|
||||
} for (product, price_unit, discount), qty in products_sold.items()]
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def render_html(self, data=None):
|
||||
company = request.env.user.company_id
|
||||
date_start = self.env.context.get('date_start', False)
|
||||
date_stop = self.env.context.get('date_stop', False)
|
||||
data = dict(data or {}, date_start=date_start, date_stop=date_stop)
|
||||
data.update(self.get_sale_details(date_start, date_stop, company))
|
||||
return self.env['report'].render('point_of_sale.report_saledetails', data)
|
||||
@@ -406,6 +406,23 @@ var ProxyStatusWidget = StatusWidget.extend({
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
/* --------- The Sale Details --------- */
|
||||
|
||||
// Generates a report to print the sales of the
|
||||
// day on a ticket
|
||||
|
||||
var SaleDetailsButton = PosBaseWidget.extend({
|
||||
template: 'SaleDetailsButton',
|
||||
start: function(){
|
||||
var self = this;
|
||||
this.$el.click(function(){
|
||||
self.pos.proxy.print_sale_details();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
/*--------------------------------------*\
|
||||
| THE CHROME |
|
||||
\*======================================*/
|
||||
@@ -662,6 +679,11 @@ var Chrome = PosBaseWidget.extend({
|
||||
'name': 'order_selector',
|
||||
'widget': OrderSelectorWidget,
|
||||
'replace': '.placeholder-OrderSelectorWidget',
|
||||
},{
|
||||
'name': 'sale_details',
|
||||
'widget': SaleDetailsButton,
|
||||
'append': '.pos-rightheader',
|
||||
'condition': function(){ return this.pos.config.use_proxy; },
|
||||
},{
|
||||
'name': 'proxy_status',
|
||||
'widget': ProxyStatusWidget,
|
||||
@@ -769,6 +791,7 @@ return {
|
||||
HeaderButtonWidget: HeaderButtonWidget,
|
||||
OrderSelectorWidget: OrderSelectorWidget,
|
||||
ProxyStatusWidget: ProxyStatusWidget,
|
||||
SaleDetailsButton: SaleDetailsButton,
|
||||
StatusWidget: StatusWidget,
|
||||
SynchNotificationWidget: SynchNotificationWidget,
|
||||
UsernameWidget: UsernameWidget,
|
||||
|
||||
@@ -2,8 +2,10 @@ odoo.define('point_of_sale.devices', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var Model = require('web.DataModel');
|
||||
var Session = require('web.Session');
|
||||
|
||||
var QWeb = core.qweb;
|
||||
var _t = core._t;
|
||||
|
||||
// the JobQueue schedules a sequence of 'jobs'. each job is
|
||||
@@ -434,6 +436,23 @@ var ProxyDevice = core.Class.extend(core.mixins.PropertiesMixin,{
|
||||
send_printing_job();
|
||||
},
|
||||
|
||||
print_sale_details: function() {
|
||||
var self = this;
|
||||
new Model('report.point_of_sale.report_saledetails').call('get_sale_details').then(function(result){
|
||||
var env = {
|
||||
company: self.pos.company,
|
||||
pos: self.pos,
|
||||
products: result.products,
|
||||
payments: result.payments,
|
||||
taxes: result.taxes,
|
||||
total_paid: result.total_paid,
|
||||
date: (new Date()).toLocaleString(),
|
||||
};
|
||||
var report = QWeb.render('SaleDetailsReport', env);
|
||||
self.print_receipt(report);
|
||||
})
|
||||
},
|
||||
|
||||
// asks the proxy to log some information, as with the debug.log you can provide several arguments.
|
||||
log: function(){
|
||||
return this.message('log',{'arguments': _.toArray(arguments)});
|
||||
|
||||
@@ -1405,13 +1405,14 @@ exports.Orderline = Backbone.Model.extend({
|
||||
},
|
||||
compute_all: function(taxes, price_unit, quantity, currency_rounding) {
|
||||
var self = this;
|
||||
var total_excluded = round_pr(price_unit * quantity, currency_rounding);
|
||||
var total_included = total_excluded;
|
||||
var base = total_excluded;
|
||||
var list_taxes = [];
|
||||
var currency_rounding_bak = currency_rounding;
|
||||
if (this.pos.company.tax_calculation_rounding_method == "round_globally"){
|
||||
currency_rounding = currency_rounding * 0.00001;
|
||||
}
|
||||
var total_excluded = round_pr(price_unit * quantity, currency_rounding);
|
||||
var total_included = total_excluded;
|
||||
var base = total_excluded;
|
||||
_(taxes).each(function(tax) {
|
||||
tax = self._map_tax_fiscal_position(tax);
|
||||
if (tax.amount_type === 'group'){
|
||||
@@ -1445,7 +1446,11 @@ exports.Orderline = Backbone.Model.extend({
|
||||
}
|
||||
}
|
||||
});
|
||||
return {taxes: list_taxes, total_excluded: total_excluded, total_included: total_included};
|
||||
return {
|
||||
taxes: list_taxes,
|
||||
total_excluded: round_pr(total_excluded, currency_rounding_bak),
|
||||
total_included: round_pr(total_included, currency_rounding_bak)
|
||||
};
|
||||
},
|
||||
get_all_prices: function(){
|
||||
var price_unit = this.get_unit_price() * (1.0 - (this.get_discount() / 100.0));
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
odoo.define('point_of_sale.report', function (require) {
|
||||
"use strict";
|
||||
|
||||
var chat_manager = require('mail.chat_manager');
|
||||
var composer = require('mail.composer');
|
||||
var ChatThread = require('mail.ChatThread');
|
||||
var utils = require('mail.utils');
|
||||
|
||||
var config = require('web.config');
|
||||
var core = require('web.core');
|
||||
var data = require('web.data');
|
||||
var Dialog = require('web.Dialog');
|
||||
var framework = require('web.framework');
|
||||
var Model = require('web.Model');
|
||||
|
||||
var pyeval = require('web.pyeval');
|
||||
var SearchView = require('web.SearchView');
|
||||
|
||||
var ActionManager = require('web.ActionManager');
|
||||
var ControlPanelMixin = require('web.ControlPanelMixin');
|
||||
var datepicker = require('web.datepicker');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var QWeb = core.qweb;
|
||||
var _t = core._t;
|
||||
|
||||
// TODO replace in v10 with proper wizard
|
||||
var PosDetailsWidget = Widget.extend(ControlPanelMixin, {
|
||||
template: 'WizardSaleDetailsReport',
|
||||
events: {
|
||||
"click .js_generate": "renderReport",
|
||||
},
|
||||
|
||||
renderElement: function() {
|
||||
this._super();
|
||||
|
||||
var current_date = new moment();
|
||||
this.dp_start = new datepicker.DateWidget(this);
|
||||
this.dp_start.insertBefore(this.$el.find('.js_date_start'));
|
||||
this.dp_start.set_value(current_date.format('YYYY-MM-DD'));
|
||||
|
||||
this.dp_stop = new datepicker.DateWidget(this);
|
||||
this.dp_stop.insertAfter(this.$el.find('.js_date_stop'));
|
||||
this.dp_stop.set_value(current_date.format('YYYY-MM-DD'));
|
||||
|
||||
},
|
||||
|
||||
renderReport: function() {
|
||||
var start = this.dp_start.get_value();
|
||||
var stop = this.dp_stop.get_value();
|
||||
var url = '/pos/sale_details_report?date_start='+start+'&date_stop='+stop;
|
||||
window.open(url, '_blank');
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
core.action_registry.add('report_pos_details', PosDetailsWidget);
|
||||
|
||||
return PosDetailsWidget;
|
||||
|
||||
});
|
||||
@@ -38,7 +38,7 @@ var PosBaseWidget = Widget.extend({
|
||||
var currency = (this.pos && this.pos.currency) ? this.pos.currency : {symbol:'$', position: 'after', rounding: 0.01, decimals: 2};
|
||||
var decimals = currency.decimals;
|
||||
|
||||
if (precision && (typeof this.pos.dp[precision]) !== undefined) {
|
||||
if (precision && this.pos.dp[precision] !== undefined) {
|
||||
decimals = this.pos.dp[precision];
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,14 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="SaleDetailsButton">
|
||||
<div class="oe_status">
|
||||
<div class="js_connected oe_icon">
|
||||
<i class='fa fa-fw fa-print'></i>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="HeaderButtonWidget">
|
||||
<div class="header-button">
|
||||
<t t-esc="widget.label" />
|
||||
@@ -846,6 +854,114 @@
|
||||
</receipt>
|
||||
</t>
|
||||
|
||||
<t t-name="SaleDetailsReport">
|
||||
<receipt align='center' width='40' value-thousands-separator='' >
|
||||
<t t-if='pos.company_logo_base64'>
|
||||
<img t-att-src='pos.company_logo_base64' />
|
||||
<br/>
|
||||
</t>
|
||||
<t t-if='!pos.company_logo_base64'>
|
||||
<h1><t t-esc='company.name' /></h1>
|
||||
<br/>
|
||||
</t>
|
||||
<br /><br />
|
||||
|
||||
<!-- Orderlines -->
|
||||
|
||||
<div class='orderlines' line-ratio='0.6'>
|
||||
<t t-foreach='products' t-as='line'>
|
||||
<line>
|
||||
<left><t t-esc='line.product_name' /></left>
|
||||
<right>
|
||||
<value value-decimals='2' value-autoint='on'>
|
||||
<t t-esc='line.quantity' />
|
||||
</value>
|
||||
<t t-if='line.uom !== "Unit(s)"'>
|
||||
<t t-esc='line.uom' />
|
||||
</t>
|
||||
</right>
|
||||
<right>
|
||||
<value><t t-esc='line.price_unit' /></value>
|
||||
</right>
|
||||
</line>
|
||||
<t t-if='line.discount !== 0'>
|
||||
<line indent='1'><left>Discount: <t t-esc='line.discount' />%</left></line>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<line><left>------------------------</left></line>
|
||||
<br/>
|
||||
|
||||
<line>
|
||||
<left>Payments:</left>
|
||||
</line>
|
||||
<line t-foreach='payments' t-as='payment'>
|
||||
<left><t t-esc='payment.name' /></left>
|
||||
<right>
|
||||
<t t-esc='payment.total'/>
|
||||
</right>
|
||||
</line>
|
||||
|
||||
<br/>
|
||||
<line><left>------------------------</left></line>
|
||||
<br/>
|
||||
|
||||
<line>
|
||||
<left>Taxes:</left>
|
||||
</line>
|
||||
<line t-foreach='taxes' t-as='taxe'>
|
||||
<left><t t-esc='taxe.name' /></left>
|
||||
<right>
|
||||
<t t-esc='taxe.total'/>
|
||||
</right>
|
||||
</line>
|
||||
|
||||
<br/>
|
||||
<line><left>------------------------</left></line>
|
||||
<br/>
|
||||
|
||||
<line>
|
||||
<left>Total:</left>
|
||||
<right>
|
||||
<t t-esc='total_paid'/>
|
||||
</right>
|
||||
</line>
|
||||
|
||||
<br/>
|
||||
<div font='b'>
|
||||
<div><t t-esc='date' /></div>
|
||||
</div>
|
||||
|
||||
</receipt>
|
||||
</t>
|
||||
|
||||
|
||||
<t t-name="WizardSaleDetailsReport">
|
||||
<div class="o_form_view"><div class="oe_form_sheetbg o_form_sheet_bg"><div class="oe_form_sheet oe_form_sheet_width o_form_sheet">
|
||||
<div class="pos_details_dates">
|
||||
<h3>Select Dates for the report</h3>
|
||||
<table class="table-condensed">
|
||||
<tr>
|
||||
<td><strong>Start date</strong></td>
|
||||
<td><div class="js_date_start"></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Stop date</strong></td>
|
||||
<td><div class="js_date_stop"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>
|
||||
<button class="js_generate">Generate report</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div></div></div>
|
||||
</t>
|
||||
|
||||
|
||||
<t t-name="PopupWidget">
|
||||
<div class="modal-dialog">
|
||||
<div class="popup popup-alert">
|
||||
|
||||
@@ -17,5 +17,11 @@
|
||||
<field name="report_name">point_of_sale.report_invoice</field>
|
||||
</record>
|
||||
|
||||
<record id="sale_details_report" model="ir.actions.report.xml">
|
||||
<field name="name">Sale Details</field>
|
||||
<field name="model">report.point_of_sale.details</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">point_of_sale.report_saledetails</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" href="/point_of_sale/static/src/less/pos_dashboard.less"/>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/tour.js"></script>
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/report.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
@@ -68,5 +68,11 @@
|
||||
<field name="context">{'group_by_no_leaf':1,'group_by':[]}</field>
|
||||
</record>
|
||||
|
||||
<record id="action_report_pos_details" model="ir.actions.client">
|
||||
<field name="name">Sale Details</field>
|
||||
<field name="tag">report_pos_details</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_report_pos_order_all" name="Orders" action="action_report_pos_order_all" parent="menu_point_rep" sequence="3"/>
|
||||
<menuitem id="menu_report_order_details" name="Sale Details" action="action_report_pos_details" parent="menu_point_rep" sequence="4"/>
|
||||
</odoo>
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<template id="report_saledetails">
|
||||
<t t-call="report.html_container">
|
||||
<t t-call="report.internal_layout">
|
||||
<div class="page">
|
||||
|
||||
<div class="text-center">
|
||||
<h2>Sale Details</h2>
|
||||
|
||||
<strong><t t-esc="date_start"/> - <t t-esc="date_stop"/></strong>
|
||||
</div>
|
||||
|
||||
<!-- Orderlines -->
|
||||
<h3>Products</h3>
|
||||
<table class="table table-condensed">
|
||||
<thead><tr>
|
||||
<th>Product</th>
|
||||
<th>Quantity</th>
|
||||
<th>Price Unit</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr t-foreach='products' t-as='line'>
|
||||
<td><t t-esc="line['product_name']" /></td>
|
||||
<td>
|
||||
<t t-esc="line['quantity']" />
|
||||
<t t-if='line["uom"] != "Unit(s)"'>
|
||||
<t t-esc='line["uom"]' />
|
||||
</t>
|
||||
</td>
|
||||
<td>
|
||||
<t t-esc='line["price_unit"]' />
|
||||
<t t-if='line["discount"] != 0'>
|
||||
Disc: <t t-esc='line["discount"]' />%
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
|
||||
<h3>Payments</h3>
|
||||
<table class="table table-condensed">
|
||||
<thead><tr>
|
||||
<th>Name</th>
|
||||
<th>Total</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr t-foreach='payments' t-as='payment'>
|
||||
<td><t t-esc="payment['name']" /></td>
|
||||
<td><t t-esc="payment['total']" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
|
||||
<h3>Taxes</h3>
|
||||
<table class="table table-condensed">
|
||||
<thead><tr>
|
||||
<th>Name</th>
|
||||
<th>Total</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr t-foreach='taxes' t-as='tax'>
|
||||
<td><t t-esc="tax['name']" /></td>
|
||||
<td><t t-esc="tax['total']" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<strong>Total: <t t-esc='total_paid'/></strong>
|
||||
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -122,6 +122,7 @@
|
||||
<field name="res_model">product.template</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to define a new product.
|
||||
|
||||
@@ -80,9 +80,10 @@ class RatingMixin(models.AbstractModel):
|
||||
def rating_get_access_token(self, partner=None):
|
||||
if not partner:
|
||||
partner = self.rating_get_partner_id()
|
||||
rated_partner = self.rating_get_rated_partner_id()
|
||||
ratings = self.rating_ids.filtered(lambda x: x.partner_id.id == partner.id and not x.consumed)
|
||||
if not ratings:
|
||||
rating = self.env['rating.rating'].create({'partner_id': partner.id, 'res_model': self._name, 'res_id': self.id})
|
||||
rating = self.env['rating.rating'].create({'partner_id': partner.id, 'rated_partner_id': rated_partner.id, 'res_model': self._name, 'res_id': self.id})
|
||||
else:
|
||||
rating = ratings[0]
|
||||
return rating.access_token
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
</p>
|
||||
<form class="form-horizontal" t-attf-action="/rating/#{token}/#{rate}/submit_feedback" method="post">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<textarea class="form-control" name="feedback" rows="8" t-esc="rating.feedback"></textarea>
|
||||
<textarea class="form-control" name="feedback" rows="8" t-att-value="rating.feedback"></textarea>
|
||||
<button type="submit" class="btn btn-primary" style="margin-top:8px;">Send Feedback</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -518,7 +518,7 @@ class ProductTemplate(models.Model):
|
||||
action = self.env.ref('stock.act_product_stock_move_open').read()[0]
|
||||
if self:
|
||||
action['context'] = {'default_product_id': products.ids[0]}
|
||||
action['domain'] = [('product_id.product_tmpl_id', 'in', products.ids)]
|
||||
action['domain'] = [('product_id.product_tmpl_id', 'in', self.ids)]
|
||||
return action
|
||||
|
||||
|
||||
|
||||
@@ -918,6 +918,7 @@ class Picking(models.Model):
|
||||
moves.action_confirm()
|
||||
return moves
|
||||
|
||||
@api.model
|
||||
def _prepare_values_extra_move(self, op, product, remaining_qty):
|
||||
"""
|
||||
Creates an extra move when there is no corresponding original move to be copied
|
||||
|
||||
@@ -99,7 +99,7 @@ def ensure_db(redirect='/web/database/selector'):
|
||||
# If the db is taken out of a query parameter, it will be checked against
|
||||
# `http.db_filter()` in order to ensure it's legit and thus avoid db
|
||||
# forgering that could lead to xss attacks.
|
||||
db = request.params.get('db')
|
||||
db = request.params.get('db') and request.params.get('db').strip()
|
||||
|
||||
# Ensure db is legit
|
||||
if db and db not in http.db_filter([db]):
|
||||
|
||||
@@ -710,6 +710,13 @@ var DataSetSearch = DataSet.extend({
|
||||
this._length -= (before - this.ids.length);
|
||||
}
|
||||
},
|
||||
add_ids: function(ids, at) {
|
||||
var before = this.ids.length;
|
||||
this._super(ids, at);
|
||||
if(this._length){
|
||||
this._length += (this.ids.length - before);
|
||||
}
|
||||
},
|
||||
unlink: function(ids, callback, error_callback) {
|
||||
var self = this;
|
||||
return this._super(ids).done(function() {
|
||||
|
||||
@@ -354,6 +354,7 @@ odoo.define('web_editor.snippets.options', function (require) {
|
||||
this.$target.off(".background-option")
|
||||
.on("background-color-event.background-option", (function (e, type) {
|
||||
e.stopPropagation();
|
||||
if (e.currentTarget !== e.target) return;
|
||||
this.$el.find("li:first > a").trigger(type);
|
||||
}).bind(this));
|
||||
return res;
|
||||
|
||||
@@ -116,6 +116,7 @@ var KanbanColumn = Widget.extend({
|
||||
} else {
|
||||
// adding record to this column
|
||||
self.records.push(record);
|
||||
self.dataset.add_ids([record.id]);
|
||||
record.setParent(self);
|
||||
ui.item.addClass('o_updating');
|
||||
self.trigger_up('kanban_column_add_record', {record: record});
|
||||
|
||||
@@ -557,9 +557,7 @@ var KanbanView = View.extend({
|
||||
active_model: this.model,
|
||||
});
|
||||
}
|
||||
this.do_execute_action(event.data, this.dataset, event.target.id).then(function () {
|
||||
self.reload_record(event.target);
|
||||
});
|
||||
this.do_execute_action(event.data, this.dataset, event.target.id, _.bind(self.reload_record, this, event.target));
|
||||
},
|
||||
|
||||
/*
|
||||
|
||||
@@ -163,33 +163,29 @@ options.registry.slider = options.Class.extend({
|
||||
return $clone;
|
||||
},
|
||||
remove_slide: function (type) {
|
||||
if(type !== "click") return;
|
||||
|
||||
if (this.remove_process) {
|
||||
return;
|
||||
}
|
||||
if (type !== "click" || this.remove_process) return;
|
||||
var self = this;
|
||||
var cycle = this.$inner.find('.item').length - 1;
|
||||
var index = this.$inner.find('.item.active').index();
|
||||
|
||||
var $items = this.$inner.find('.item');
|
||||
var cycle = $items.length - 1;
|
||||
var $active = $items.filter('.active');
|
||||
var index = $active.index();
|
||||
|
||||
if (cycle > 0) {
|
||||
this.remove_process = true;
|
||||
var $el = this.$inner.find('.item.active');
|
||||
self.$target.on('slid.bs.carousel', function (event) {
|
||||
$el.remove();
|
||||
this.$target.on('slid.bs.carousel.slide_removal', function (event) {
|
||||
$active.remove();
|
||||
self.$indicators.find("li:last").remove();
|
||||
self.$target.off('slid.bs.carousel');
|
||||
self.$target.off('slid.bs.carousel.slide_removal');
|
||||
self.rebind_event();
|
||||
self.remove_process = false;
|
||||
if (cycle === 1) {
|
||||
self.on_remove_slide(event);
|
||||
self.$target.find('.carousel-control, .carousel-indicators').addClass("hidden");
|
||||
}
|
||||
});
|
||||
setTimeout(function () {
|
||||
self.$target.carousel( index > 0 ? --index : cycle );
|
||||
}, 500);
|
||||
} else {
|
||||
this.$target.find('.carousel-control, .carousel-indicators').addClass("hidden");
|
||||
_.defer(function () {
|
||||
self.$target.carousel(index > 0 ? --index : cycle);
|
||||
});
|
||||
}
|
||||
},
|
||||
interval : function (type, value) {
|
||||
@@ -217,18 +213,24 @@ options.registry.carousel = options.registry.slider.extend({
|
||||
},
|
||||
start : function () {
|
||||
var self = this;
|
||||
this._super();
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
// set background and prepare to clean for save
|
||||
this.$target.on('slid.bs.carousel', function () {
|
||||
if(self.editor && self.editor.styles.background) {
|
||||
self.editor.styles.background.$target = self.$target.find(".item.active");
|
||||
self.editor.styles.background_position.$target = self.editor.styles.background.$target;
|
||||
self.editor.styles.background.set_active();
|
||||
self.editor.styles.background_position.set_active();
|
||||
self.editor.styles.background.$target.trigger("snippet-option-change", [self.editor.styles.background]);
|
||||
}
|
||||
self.$target.carousel("pause");
|
||||
if (!self.editor) return;
|
||||
|
||||
_.each(["background", "background_position", "colorpicker"], function (opt_name) {
|
||||
var s_option = self.editor.styles[opt_name];
|
||||
if (!s_option) return;
|
||||
|
||||
s_option.$target = self.$target.find(".item.active");
|
||||
s_option.set_active();
|
||||
s_option.$target.trigger("snippet-option-change", [s_option]);
|
||||
});
|
||||
});
|
||||
this.$target.trigger('slid.bs.carousel');
|
||||
},
|
||||
|
||||
@@ -26,7 +26,7 @@ class Project(models.Model):
|
||||
continue
|
||||
if recipient.user_ids and all(recipient.user_ids.mapped('share')):
|
||||
group_data['user'] |= recipient
|
||||
done_ids.add(recipient.id)
|
||||
done_ids.add(recipient.id)
|
||||
return super(Project, self)._notification_group_recipients(message, recipients, done_ids, group_data)
|
||||
|
||||
|
||||
@@ -53,6 +53,6 @@ class Task(models.Model):
|
||||
continue
|
||||
if recipient.user_ids and all(recipient.user_ids.mapped('share')):
|
||||
group_data['user'] |= recipient
|
||||
done_ids.add(recipient.id)
|
||||
done_ids.add(recipient.id)
|
||||
return super(Task, self)._notification_group_recipients(message, recipients, done_ids, group_data)
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ class Issue(models.Model):
|
||||
continue
|
||||
if recipient.user_ids and group_project_user_id in recipient.user_ids[0].groups_id.ids:
|
||||
group_data['group_project_user'] |= recipient
|
||||
done_ids.add(recipient.id)
|
||||
elif recipient.user_ids and all(recipient.user_ids.mapped('share')):
|
||||
group_data['user'] |= recipient
|
||||
done_ids.add(recipient.id)
|
||||
done_ids.add(recipient.id)
|
||||
return super(Issue, self)._notification_group_recipients(message, recipients, done_ids, group_data)
|
||||
|
||||
@@ -15,7 +15,7 @@ class AccountInvoice(models.Model):
|
||||
continue
|
||||
if recipient.user_ids and all(recipient.user_ids.mapped('share')):
|
||||
group_data['user'] |= recipient
|
||||
done_ids.add(recipient.id)
|
||||
done_ids.add(recipient.id)
|
||||
return super(AccountInvoice, self)._notification_group_recipients(message, recipients, done_ids, group_data)
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -166,7 +166,7 @@ class SaleOrder(models.Model):
|
||||
continue
|
||||
if recipient.user_ids and all(recipient.user_ids.mapped('share')):
|
||||
group_data['user'] |= recipient
|
||||
done_ids.add(recipient.id)
|
||||
done_ids.add(recipient.id)
|
||||
return super(SaleOrder, self)._notification_group_recipients(message, recipients, done_ids, group_data)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user