[MERGE] forward port branch saas-11 up to 2a6e077f4a
This commit is contained in:
@@ -167,7 +167,7 @@ class AccountAccount(models.Model):
|
||||
@api.returns('self', lambda value: value.id)
|
||||
def copy(self, default=None):
|
||||
default = dict(default or {})
|
||||
default.update(code=_("%s (copy)") % (self.code or ''))
|
||||
default.setdefault('code', _("%s (copy)") % (self.code or ''))
|
||||
return super(AccountAccount, self).copy(default)
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -392,7 +392,15 @@ class account_payment(models.Model):
|
||||
#Reconcile with the invoices
|
||||
if self.payment_difference_handling == 'reconcile' and self.payment_difference:
|
||||
writeoff_line = self._get_shared_move_line_vals(0, 0, 0, move.id, False)
|
||||
debit_wo, credit_wo, amount_currency_wo, currency_id = aml_obj.with_context(date=self.payment_date).compute_amount_fields(self.payment_difference, self.currency_id, self.company_id.currency_id, invoice_currency)
|
||||
amount_currency_wo, currency_id = aml_obj.with_context(date=self.payment_date).compute_amount_fields(self.payment_difference, self.currency_id, self.company_id.currency_id, invoice_currency)[2:]
|
||||
# the writeoff debit and credit must be computed from the invoice residual in company currency
|
||||
# minus the payment amount in company currency, and not from the payment difference in the payment currency
|
||||
# to avoid loss of precision during the currency rate computations. See revision 20935462a0cabeb45480ce70114ff2f4e91eaf79 for a detailed example.
|
||||
total_residual_company_signed = sum(invoice.residual_company_signed for invoice in self.invoice_ids)
|
||||
total_payment_company_signed = self.currency_id.with_context(date=self.payment_date).compute(self.amount, self.company_id.currency_id)
|
||||
amount_wo = total_residual_company_signed - total_payment_company_signed
|
||||
debit_wo = amount_wo > 0 and amount_wo or 0.0
|
||||
credit_wo = amount_wo < 0 and -amount_wo or 0.0
|
||||
writeoff_line['name'] = _('Counterpart')
|
||||
writeoff_line['account_id'] = self.writeoff_account_id.id
|
||||
writeoff_line['debit'] = debit_wo
|
||||
|
||||
@@ -75,7 +75,7 @@ class AccountInvoiceRefund(models.TransientModel):
|
||||
for tmpline in refund.move_id.line_ids:
|
||||
if tmpline.account_id.id == inv.account_id.id:
|
||||
to_reconcile_lines += tmpline
|
||||
to_reconcile_lines.reconcile()
|
||||
to_reconcile_lines.filtered(lambda l: l.reconciled == False).reconcile()
|
||||
if mode == 'modify':
|
||||
invoice = inv.read(
|
||||
['name', 'type', 'number', 'reference',
|
||||
|
||||
@@ -65,7 +65,10 @@ class AccountInvoiceLine(models.Model):
|
||||
return res
|
||||
|
||||
def _set_additional_fields(self, invoice):
|
||||
rec = self.env['account.analytic.default'].account_get(self.product_id.id, self.invoice_id.partner_id.id, self.env.uid,
|
||||
fields.Date.today(), company_id=self.company_id.id)
|
||||
self.account_analytic_id = rec.analytic_id.id
|
||||
if not self.account_analytic_id:
|
||||
rec = self.env['account.analytic.default'].account_get(
|
||||
self.product_id.id, self.invoice_id.partner_id.id, self.env.uid,
|
||||
fields.Date.today(), company_id=self.company_id.id)
|
||||
if rec:
|
||||
self.account_analytic_id = rec.analytic_id.id
|
||||
super(AccountInvoiceLine, self)._set_additional_fields(invoice)
|
||||
|
||||
@@ -244,7 +244,7 @@
|
||||
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
|
||||
<separator/>
|
||||
<filter string="Unconfirmed" name="draft" domain="[('state','=','draft')]" help="Events in New state"/>
|
||||
<filter string="Confirmed" domain="[('state','=','confirm')]" help="Confirmed events"/>
|
||||
<filter name="confirm" string="Confirmed" domain="[('state','=','confirm')]" help="Confirmed events"/>
|
||||
<separator/>
|
||||
<filter string="My Events" help="My Events" domain="[('user_id','=',uid)]"/>
|
||||
<separator/>
|
||||
|
||||
@@ -12,18 +12,17 @@ from odoo.tools.safe_eval import safe_eval
|
||||
EXPRESSION_PATTERN = re.compile('(\$\{.+?\})')
|
||||
|
||||
|
||||
def _reopen(self, res_id, model):
|
||||
def _reopen(self, res_id, model, context=None):
|
||||
# save original model in context, because selecting the list of available
|
||||
# templates requires a model in context
|
||||
context = dict(context or {}, default_model=model)
|
||||
return {'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'view_type': 'form',
|
||||
'res_id': res_id,
|
||||
'res_model': self._name,
|
||||
'target': 'new',
|
||||
# save original model in context, because selecting the list of available
|
||||
# templates requires a model in context
|
||||
'context': {
|
||||
'default_model': model,
|
||||
},
|
||||
'context': context,
|
||||
}
|
||||
|
||||
|
||||
@@ -409,7 +408,7 @@ class MailComposer(models.TransientModel):
|
||||
# generate the saved template
|
||||
record.write({'template_id': template.id})
|
||||
record.onchange_template_id_wrapper()
|
||||
return _reopen(self, record.id, record.model)
|
||||
return _reopen(self, record.id, record.model, context=self._context)
|
||||
|
||||
#------------------------------------------------------
|
||||
# Template rendering
|
||||
|
||||
@@ -6,8 +6,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 10.0c\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-09-07 08:57+0000\n"
|
||||
"PO-Revision-Date: 2016-09-07 08:57+0000\n"
|
||||
"POT-Creation-Date: 2016-12-14 14:52+0000\n"
|
||||
"PO-Revision-Date: 2016-12-14 14:52+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -15,6 +15,18 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: payment_transfer
|
||||
#: code:addons/payment_transfer/models/payment_acquirer.py:32
|
||||
#, python-format
|
||||
msgid "<div>\n"
|
||||
"<h3>Please use the following transfer details</h3>\n"
|
||||
"<h4>%(bank_title)s</h4>\n"
|
||||
"%(bank_accounts)s\n"
|
||||
"<h4>Communication</h4>\n"
|
||||
"<p>Please use the order name as communication reference.</p>\n"
|
||||
"</div>"
|
||||
msgstr ""
|
||||
|
||||
#. module: payment_transfer
|
||||
#: code:addons/payment_transfer/models/payment.py:63
|
||||
#, python-format
|
||||
|
||||
@@ -25,13 +25,13 @@ class TransferPaymentAcquirer(models.Model):
|
||||
accounts = journals.mapped('bank_account_id').name_get()
|
||||
bank_title = _('Bank Accounts') if len(accounts) > 1 else _('Bank Account')
|
||||
bank_accounts = ''.join(['<ul>'] + ['<li>%s</li>' % name for id, name in accounts] + ['</ul>'])
|
||||
post_msg = '''<div>
|
||||
post_msg = _('''<div>
|
||||
<h3>Please use the following transfer details</h3>
|
||||
<h4>%(bank_title)s</h4>
|
||||
%(bank_accounts)s
|
||||
<h4>Communication</h4>
|
||||
<p>Please use the order name as communication reference.</p>
|
||||
</div>''' % {
|
||||
</div>''') % {
|
||||
'bank_title': bank_title,
|
||||
'bank_accounts': bank_accounts,
|
||||
}
|
||||
|
||||
@@ -53,36 +53,29 @@ class PosOrder(models.Model):
|
||||
}
|
||||
|
||||
# This deals with orders that belong to a closed session. In order
|
||||
# to recover from this we:
|
||||
# - assign the order to another compatible open session
|
||||
# - if that doesn't exist, create a new one
|
||||
# to recover from this situation we create a new rescue session,
|
||||
# making it obvious that something went wrong.
|
||||
# A new, separate, rescue session is preferred for every such recovery,
|
||||
# to avoid adding unrelated orders to live sessions.
|
||||
def _get_valid_session(self, order):
|
||||
PosSession = self.env['pos.session']
|
||||
closed_session = PosSession.browse(order['pos_session_id'])
|
||||
open_session = PosSession.search(
|
||||
[('state', '=', 'opened'),
|
||||
('config_id', '=', closed_session.config_id.id),
|
||||
('user_id', '=', closed_session.user_id.id)],
|
||||
limit=1, order="start_at DESC")
|
||||
|
||||
_logger.warning('session %s (ID: %s) was closed but received order %s (total: %s) belonging to it',
|
||||
closed_session.name,
|
||||
closed_session.id,
|
||||
order['name'],
|
||||
order['amount_total'])
|
||||
_logger.warning('attempting to create recovery session for saving order %s', order['name'])
|
||||
new_session = PosSession.create({
|
||||
'config_id': closed_session.config_id.id,
|
||||
'name': _('(RESCUE FOR %(session)s)') % {'session': closed_session.name},
|
||||
'rescue': True, # avoid conflict with live sessions
|
||||
})
|
||||
# bypass opening_control (necessary when using cash control)
|
||||
new_session.action_pos_session_open()
|
||||
|
||||
if open_session:
|
||||
_logger.warning('using session %s (ID: %s) for order %s instead',
|
||||
open_session.name,
|
||||
open_session.id,
|
||||
order['name'])
|
||||
return open_session
|
||||
else:
|
||||
_logger.warning('attempting to create new session for order %s', order['name'])
|
||||
new_session = PosSession.create({'config_id': closed_session.config_id.id})
|
||||
# bypass opening_control (necessary when using cash control)
|
||||
new_session.action_pos_session_open()
|
||||
return new_session
|
||||
return new_session
|
||||
|
||||
def _match_payment_to_invoice(self, order):
|
||||
account_precision = self.env['decimal.precision'].precision_get('Account')
|
||||
@@ -408,7 +401,7 @@ class PosOrder(models.Model):
|
||||
# set name based on the sequence specified on the config
|
||||
session = self.env['pos.session'].browse(values['session_id'])
|
||||
values['name'] = session.config_id.sequence_id._next()
|
||||
values.setdefault('session_id', session.config_id.pricelist_id.id)
|
||||
values.setdefault('pricelist_id', session.config_id.pricelist_id.id)
|
||||
else:
|
||||
# fallback on any pos.order sequence
|
||||
values['name'] = self.env['ir.sequence'].next_by_code('pos.order')
|
||||
|
||||
@@ -165,6 +165,8 @@ class PosSession(models.Model):
|
||||
pos_config.sudo().write({'journal_ids': [(6, 0, journals.ids)]})
|
||||
|
||||
pos_name = self.env['ir.sequence'].with_context(ctx).next_by_code('pos.session')
|
||||
if values.get('name'):
|
||||
pos_name += ' ' + values['name']
|
||||
|
||||
statements = []
|
||||
ABS = self.env['account.bank.statement']
|
||||
@@ -269,3 +271,33 @@ class PosSession(models.Model):
|
||||
'target': 'self',
|
||||
'url': '/pos/web/',
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def open_cashbox(self):
|
||||
self.ensure_one()
|
||||
context = dict(self._context)
|
||||
balance_type = context.get('balance') or 'start'
|
||||
context['bank_statement_id'] = self.cash_register_id.id
|
||||
context['balance'] = balance_type
|
||||
context['default_pos_id'] = self.config_id.id
|
||||
|
||||
action = {
|
||||
'name': _('Cash Control'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'account.bank.statement.cashbox',
|
||||
'view_id': self.env.ref('account.view_account_bnk_stmt_cashbox').id,
|
||||
'type': 'ir.actions.act_window',
|
||||
'context': context,
|
||||
'target': 'new'
|
||||
}
|
||||
|
||||
cashbox_id = None
|
||||
if balance_type == 'start':
|
||||
cashbox_id = self.cash_register_id.cashbox_start_id.id
|
||||
else:
|
||||
cashbox_id = self.cash_register_id.cashbox_end_id.id
|
||||
if cashbox_id:
|
||||
action['res_id'] = cashbox_id
|
||||
|
||||
return action
|
||||
|
||||
@@ -114,7 +114,7 @@ var Gui = core.Class.extend({
|
||||
this.current_screen.hide();
|
||||
}
|
||||
this.current_screen = screen;
|
||||
this.current_screen.show();
|
||||
this.current_screen.show(refresh);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -1075,7 +1075,8 @@ exports.Orderline = Backbone.Model.extend({
|
||||
init_from_JSON: function(json) {
|
||||
this.product = this.pos.db.get_product_by_id(json.product_id);
|
||||
if (!this.product) {
|
||||
console.error('ERROR: attempting to recover product not available in the point of sale');
|
||||
console.error('ERROR: attempting to recover product ID', json.product_id,
|
||||
'not available in the point of sale. Correct the product or clean the browser cache.');
|
||||
}
|
||||
this.set_product_lot(this.product)
|
||||
this.price = json.price_unit;
|
||||
|
||||
@@ -272,8 +272,9 @@ var ScaleScreenWidget = ScreenWidget.extend({
|
||||
});
|
||||
|
||||
this.$('.next,.buy-product').click(function(){
|
||||
self.order_product();
|
||||
self.gui.show_screen(self.next_screen);
|
||||
// add product *after* switching screen to scroll properly
|
||||
self.order_product();
|
||||
});
|
||||
|
||||
queue.schedule(function(){
|
||||
@@ -964,10 +965,12 @@ var ProductScreenWidget = ScreenWidget.extend({
|
||||
}
|
||||
},
|
||||
|
||||
show: function(){
|
||||
show: function(reset){
|
||||
this._super();
|
||||
this.product_categories_widget.reset_category();
|
||||
this.numpad.state.reset();
|
||||
if (reset) {
|
||||
this.product_categories_widget.reset_category();
|
||||
this.numpad.state.reset();
|
||||
}
|
||||
},
|
||||
|
||||
close: function(){
|
||||
|
||||
@@ -48,18 +48,18 @@
|
||||
<span class="o_stat_text">Money Out</span>
|
||||
</div>
|
||||
</button>
|
||||
<button name="%(account.action_view_account_bnk_stmt_cashbox)d"
|
||||
<button name="open_cashbox"
|
||||
class="oe_stat_button"
|
||||
attrs="{'invisible':['|', ('cash_control', '=', False), ('state', '!=', 'opening_control')]}"
|
||||
icon="fa-money"
|
||||
type="action" context="{'balance': 'start', 'bank_statement_id': cash_register_id, 'default_pos_id': config_id}">
|
||||
type="object" context="{'balance': 'start'}">
|
||||
<span class="o_stat_text">Set Opening Balance</span>
|
||||
</button>
|
||||
<button name="%(account.action_view_account_bnk_stmt_cashbox)d"
|
||||
<button name="open_cashbox"
|
||||
class="oe_stat_button"
|
||||
attrs="{'invisible':['|', '|', ('cash_control', '=', False), ('state', '=', 'opening_control'), ('state', '=', 'closed')]}"
|
||||
icon="fa-money"
|
||||
type="action" context="{'balance': 'end', 'bank_statement_id': cash_register_id, 'default_pos_id': config_id}">
|
||||
type="object" context="{'balance': 'end'}">
|
||||
<span class="o_stat_text">Set Closing Balance</span>
|
||||
</button>
|
||||
<button class="oe_stat_button" name="action_stock_picking" type="object" icon="fa-arrows-v" attrs="{'invisible':[('picking_count', '=', 0)]}">
|
||||
|
||||
@@ -262,7 +262,8 @@ class Report(models.Model):
|
||||
return self._run_wkhtmltopdf(
|
||||
headerhtml, footerhtml, contenthtml, context.get('landscape'),
|
||||
paperformat, specific_paperformat_args, save_in_attachment,
|
||||
context.get('set_viewport_size')
|
||||
context.get('set_viewport_size'),
|
||||
context
|
||||
)
|
||||
|
||||
@api.noguess
|
||||
|
||||
@@ -233,3 +233,37 @@ class TestSaleStock(TestSale):
|
||||
|
||||
self.so.action_done()
|
||||
self.assertEqual(self.so.invoice_status, 'invoiced', 'Sale Stock: so invoice_status should be "invoiced" when set to done')
|
||||
|
||||
def test_04_create_picking_update_saleorderline(self):
|
||||
"""
|
||||
Test that updating multiple sale order lines after a succesful delivery creates a single picking containing
|
||||
the new move lines.
|
||||
"""
|
||||
# sell two products
|
||||
item1 = self.products['prod_order']
|
||||
item2 = self.products['prod_del']
|
||||
|
||||
self.so = self.env['sale.order'].create({
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [
|
||||
(0, 0, {'name': item1.name, 'product_id': item1.id, 'product_uom_qty': 1, 'product_uom': item1.uom_id.id, 'price_unit': item1.list_price}),
|
||||
(0, 0, {'name': item2.name, 'product_id': item2.id, 'product_uom_qty': 1, 'product_uom': item2.uom_id.id, 'price_unit': item2.list_price}),
|
||||
],
|
||||
})
|
||||
self.so.action_confirm()
|
||||
|
||||
# deliver them
|
||||
self.assertEquals(len(self.so.picking_ids), 1)
|
||||
self.so.picking_ids[0].action_done()
|
||||
self.assertEquals(self.so.picking_ids[0].state, "done")
|
||||
|
||||
# update the two original sale order lines
|
||||
self.so.write({
|
||||
'order_line': [
|
||||
(1, self.so.order_line[0].id, {'product_uom_qty': 2}),
|
||||
(1, self.so.order_line[1].id, {'product_uom_qty': 2}),
|
||||
]
|
||||
})
|
||||
|
||||
# a single picking should be created for the new delivery
|
||||
self.assertEquals(len(self.so.picking_ids), 2)
|
||||
|
||||
@@ -411,6 +411,14 @@ class StockMove(models.Model):
|
||||
type (moves should already have them identical). Otherwise, create a new
|
||||
picking to assign them to. """
|
||||
Picking = self.env['stock.picking']
|
||||
|
||||
# If this method is called in batch by a write on a one2many and
|
||||
# at some point had to create a picking, some next iterations could
|
||||
# try to find back the created picking. As we look for it by searching
|
||||
# on some computed fields, we have to force a recompute, else the
|
||||
# record won't be found.
|
||||
self.recompute()
|
||||
|
||||
for move in self:
|
||||
picking = Picking.search([
|
||||
('group_id', '=', move.group_id.id),
|
||||
|
||||
@@ -381,7 +381,7 @@ class Picking(models.Model):
|
||||
elif self.partner_id:
|
||||
location_dest_id = self.partner_id.property_stock_customer.id
|
||||
else:
|
||||
customerloc, location_dest_id = self.env['stock.warehouse']._get_partner_locations()
|
||||
location_dest_id, supplierloc = self.env['stock.warehouse']._get_partner_locations()
|
||||
|
||||
self.location_id = location_id
|
||||
self.location_dest_id = location_dest_id
|
||||
|
||||
@@ -1007,6 +1007,7 @@ var FieldReference = common.AbstractField.extend(common.ReinitializeFieldMixin,
|
||||
this.m2o = new FieldMany2One(this.fm, { attrs: {
|
||||
name: 'Referenced Document',
|
||||
modifiers: JSON.stringify({readonly: this.get('effective_readonly')}),
|
||||
context: this.build_context().eval(),
|
||||
}});
|
||||
this.m2o.on("change:value", this, this.data_changed);
|
||||
this.m2o.appendTo(this.$el);
|
||||
|
||||
@@ -127,6 +127,11 @@
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
&.o_list_record_delete {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.o_readonly {
|
||||
visibility: visible;
|
||||
color: transparent;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Denmark, 2016-05-06
|
||||
|
||||
I hereby agree to the terms of the Odoo Individual Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Hans Henrik Gabelgaard hhg@gabelgaard.org https://github.com/hhgabelgaard
|
||||
Reference in New Issue
Block a user