[MERGE] forward port branch 11.0 up to 4df568549a

This commit is contained in:
Christophe Simonis
2019-04-02 16:35:17 +02:00
15 changed files with 140 additions and 24 deletions
@@ -233,14 +233,19 @@ class AccountBankStatement(models.Model):
statements = self.filtered(lambda r: r.state == 'open')
for statement in statements:
moves = self.env['account.move']
# `line.journal_entry_ids` gets invalidated from the cache during the loop
# because new move lines are being created at each iteration.
# The below dict is to prevent the ORM to permanently refetch `line.journal_entry_ids`
line_journal_entries = {line: line.journal_entry_ids for line in statement.line_ids}
for st_line in statement.line_ids:
#upon bank statement confirmation, look if some lines have the account_id set. It would trigger a journal entry
#creation towards that account, with the wanted side-effect to skip that line in the bank reconciliation widget.
journal_entries = line_journal_entries[st_line]
if st_line.account_id and not journal_entries.ids:
st_line.fast_counterpart_creation()
if not st_line.account_id and not st_line.journal_entry_ids.ids and not st_line.statement_id.currency_id.is_zero(st_line.amount):
if not st_line.account_id and not journal_entries.ids and not st_line.statement_id.currency_id.is_zero(st_line.amount):
raise UserError(_('All the account entries lines must be processed in order to close the statement.'))
for aml in st_line.journal_entry_ids:
moves |= aml.move_id
moves = statement.mapped('line_ids.journal_entry_ids.move_id')
if moves:
moves.filtered(lambda m: m.state != 'posted').post()
statement.message_post(body=_('Statement %s confirmed, journal items were created.') % (statement.name,))
+3 -2
View File
@@ -1576,12 +1576,13 @@ class AccountPartialReconcile(models.Model):
# Get value of matched percentage from both move before reconciliating
lines = self.env['account.move.line'].browse(aml)
lines._payment_invoice_match()
if lines[0].account_id.internal_type in ('receivable', 'payable'):
tax_cash_basis_entry = not self.env.context.get('skip_tax_cash_basis_entry') and lines[0].account_id.internal_type in ('receivable', 'payable')
if tax_cash_basis_entry:
percentage_before_rec = lines._get_matched_percentage()
# Reconcile
res = super(AccountPartialReconcile, self).create(vals)
# if the reconciliation is a matching on a receivable or payable account, eventually create a tax cash basis entry
if lines[0].account_id.internal_type in ('receivable', 'payable'):
if tax_cash_basis_entry:
res.create_tax_cash_basis_entry(percentage_before_rec)
res._compute_partial_lines()
return res
@@ -552,7 +552,10 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
type: 'float',
name: 'amount',
}], {
account_id: {string: _t("Account")},
account_id: {
string: _t("Account"),
domain: [['deprecated', '=', false]],
},
label: {string: _t("Label")},
amount: {string: _t("Account")}
}).then(function (recordID) {
@@ -54,6 +54,7 @@ var db = {
code: {string: "code", type: 'integer'},
name: {string: "Displayed name", type: 'char'},
company_id: {string: "Company", type: 'many2one', relation: 'res.company'},
deprecated: {string: "Deprecated", type: 'boolean'},
},
records: [
{id: 282, code: 100000, name: "100000 Fixed Asset Account", company_id: 1},
@@ -30,7 +30,13 @@ def geo_find(addr, apikey=False):
if result['status'] != 'OK':
if result.get('error_message'):
_logger.error(result['error_message'])
return None
error_msg = _('Unable to geolocate, received the error:\n%s'
'\n\nGoogle made this a paid feature.\n'
'You should first enable billing on your Google account.\n'
'Then, go to Developer Console, and enable the APIs:\n'
'Geocoding, Maps Static, Maps Javascript.\n'
% result['error_message'])
raise UserError(error_msg)
try:
geo = result['results'][0]['geometry']['location']
+2 -2
View File
@@ -1,6 +1,6 @@
id,name,description,chart_template_id/id,type_tax_use,amount_type,amount,price_include,sequence,include_base_amount,account_id/id,refund_account_id/id,tag_ids/id,tax_group_id/id
tax_in_e,仮受消費税(外),仮受消費税(外),l10n_jp1,sale,percent,8,FALSE,1,FALSE,A21807,A21807,"tag_jp_03,tag_jp_12",tax_group_8
tax_in_i,仮受消費税(内),仮受消費税(内),l10n_jp1,sale,percent,8,TRUE,1,FALSE,A21807,A21807,"tag_jp_03,tag_jp_12",tax_group_8
tax_in_e,仮受消費税(外),仮受消費税(外),l10n_jp1,sale,percent,8,FALSE,1,FALSE,A21809,A21809,"tag_jp_03,tag_jp_12",tax_group_8
tax_in_i,仮受消費税(内),仮受消費税(内),l10n_jp1,sale,percent,8,TRUE,1,FALSE,A21809,A21809,"tag_jp_03,tag_jp_12",tax_group_8
tax_in_x,輸出免税,輸出免税,l10n_jp1,sale,percent,,FALSE,1,FALSE,,,"tag_jp_04,tag_jp_13",tax_group_0
tax_in_o,非課税販売,非課税販売,l10n_jp1,sale,percent,,FALSE,1,FALSE,,,"tag_jp_09,tag_jp_14",tax_group_0
tax_out_e,仮払消費税(外),仮払消費税(外),l10n_jp1,purchase,percent,8,FALSE,1,FALSE,A11807,A11807,"tag_jp_07,tag_jp_16",tax_group_8
1 id name description chart_template_id/id type_tax_use amount_type amount price_include sequence include_base_amount account_id/id refund_account_id/id tag_ids/id tax_group_id/id
2 tax_in_e 仮受消費税(外) 仮受消費税(外) l10n_jp1 sale percent 8 FALSE 1 FALSE A21807 A21809 A21807 A21809 tag_jp_03,tag_jp_12 tax_group_8
3 tax_in_i 仮受消費税(内) 仮受消費税(内) l10n_jp1 sale percent 8 TRUE 1 FALSE A21807 A21809 A21807 A21809 tag_jp_03,tag_jp_12 tax_group_8
4 tax_in_x 輸出免税 輸出免税 l10n_jp1 sale percent FALSE 1 FALSE tag_jp_04,tag_jp_13 tax_group_0
5 tax_in_o 非課税販売 非課税販売 l10n_jp1 sale percent FALSE 1 FALSE tag_jp_09,tag_jp_14 tax_group_0
6 tax_out_e 仮払消費税(外) 仮払消費税(外) l10n_jp1 purchase percent 8 FALSE 1 FALSE A11807 A11807 tag_jp_07,tag_jp_16 tax_group_8
+4
View File
@@ -34,6 +34,10 @@
<field name="name">DIOT: 8%</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_diot_8_non_cre" model="account.account.tag">
<field name="name">DIOT: 8% NO ACREDITABLE</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_diot_ret" model="account.account.tag">
<field name="name">DIOT: Retención</field>
<field name="applicability">taxes</field>
+8 -1
View File
@@ -483,6 +483,7 @@ class PosOrder(models.Model):
return - price_unit
def _reconcile_payments(self):
cash_basis_percentage_before_rec = {move: move.line_ids._get_matched_percentage() for move in self.mapped('account_move')}
for order in self:
aml = order.statement_ids.mapped('journal_entry_ids') | order.account_move.line_ids | order.invoice_id.move_id.line_ids
aml = aml.filtered(lambda r: not r.reconciled and r.account_id.internal_type == 'receivable' and r.partner_id == order.partner_id.commercial_partner_id)
@@ -491,7 +492,7 @@ class PosOrder(models.Model):
# Cash returns will be well reconciled
# Whereas freight returns won't be
# "c'est la vie..."
aml.reconcile()
aml.with_context(skip_tax_cash_basis_entry=True).reconcile()
except Exception:
# There might be unexpected situations where the automatic reconciliation won't
# work. We don't want the user to be blocked because of this, since the automatic
@@ -500,6 +501,12 @@ class PosOrder(models.Model):
# It may be interesting to have the Traceback logged anyway
# for debugging and support purposes
_logger.exception('Reconciliation did not work for order %s', order.name)
for move in self.mapped('account_move'):
partial_reconcile = self.env['account.partial.reconcile'].search([
'|',
('credit_move_id.move_id', '=', move.id),
('debit_move_id.move_id', '=', move.id)], limit=1)
partial_reconcile.create_tax_cash_basis_entry(cash_basis_percentage_before_rec[move])
def _filtered_for_reconciliation(self):
filter_states = ['invoiced', 'done']
+2 -1
View File
@@ -274,6 +274,7 @@ class PosSession(models.Model):
for session in self:
company_id = session.config_id.company_id.id
ctx = dict(self.env.context, force_company=company_id, company_id=company_id)
ctx_notrack = dict(ctx, mail_notrack=True)
for st in session.statement_ids:
if abs(st.difference) > st.journal_id.amount_authorized_diff:
# The pos manager can close statements with maximums.
@@ -281,7 +282,7 @@ class PosSession(models.Model):
raise UserError(_("Your ending balance is too different from the theoretical cash closing (%.2f), the maximum allowed is: %.2f. You can contact your manager to force it.") % (st.difference, st.journal_id.amount_authorized_diff))
if (st.journal_id.type not in ['bank', 'cash']):
raise UserError(_("The type of the journal for your payment method should be bank or cash "))
st.with_context(ctx).sudo().button_confirm_bank()
st.with_context(ctx_notrack).sudo().button_confirm_bank()
self.with_context(ctx)._confirm_orders()
self.write({'state': 'closed'})
return {
+30 -4
View File
@@ -155,24 +155,50 @@ class StockMove(models.Model):
@api.model
def _get_in_base_domain(self, company_id=False):
# Domain:
# - state is done
# - coming from a location without company, or an inventory location within the same company
# - going to a location within the same company
domain = [
('state', '=', 'done'),
('location_id.company_id', '=', False),
('location_dest_id.company_id', '=', company_id or self.env.user.company_id.id)
'&',
'|',
('location_id.company_id', '=', False),
'&',
('location_id.usage', 'in', ['inventory', 'production']),
('location_id.company_id', '=', company_id or self.env.user.company_id.id),
('location_dest_id.company_id', '=', company_id or self.env.user.company_id.id),
]
return domain
@api.model
def _get_all_base_domain(self, company_id=False):
# Domain:
# - state is done
# Then, either 'in' or 'out' moves.
# 'in' moves:
# - coming from a location without company, or an inventory location within the same company
# - going to a location within the same company
# 'out' moves:
# - coming from to a location within the same company
# - going to a location without company, or an inventory location within the same company
domain = [
('state', '=', 'done'),
'|',
'&',
('location_id.company_id', '=', False),
'|',
('location_id.company_id', '=', False),
'&',
('location_id.usage', 'in', ['inventory', 'production']),
('location_id.company_id', '=', company_id or self.env.user.company_id.id),
('location_dest_id.company_id', '=', company_id or self.env.user.company_id.id),
'&',
('location_id.company_id', '=', company_id or self.env.user.company_id.id),
('location_dest_id.company_id', '=', False)
'|',
('location_dest_id.company_id', '=', False),
'&',
('location_dest_id.usage', '=', 'inventory'),
('location_dest_id.company_id', '=', company_id or self.env.user.company_id.id),
]
return domain
@@ -14,6 +14,7 @@ class TestStockValuation(TransactionCase):
self.stock_location = self.env.ref('stock.stock_location_stock')
self.customer_location = self.env.ref('stock.stock_location_customers')
self.supplier_location = self.env.ref('stock.stock_location_suppliers')
self.inventory_location = self.env.ref('stock.location_inventory')
self.partner = self.env['res.partner'].create({'name': 'xxx'})
self.owner1 = self.env['res.partner'].create({'name': 'owner1'})
self.uom_unit = self.env.ref('uom.product_uom_unit')
@@ -3905,3 +3906,47 @@ class TestStockValuation(TransactionCase):
self.assertEqual(self.product1.with_context(to_date=Date.to_string(date6)).stock_value, 100)
self.assertEqual(self.product1.qty_at_date, 10)
self.assertEqual(self.product1.stock_value, 100)
def test_inventory_fifo_1(self):
""" Make an inventory from a location with a company set, and ensure the product has a stock
value. When the product is sold, ensure there is no remaining quantity on the original move
and no stock value.
"""
self.product1.standard_price = 15
self.product1.product_tmpl_id.cost_method = 'fifo'
self.inventory_location.company_id = self.env.user.company_id.id
# Start Inventory: 12 units
move1 = self.env['stock.move'].create({
'name': 'Adjustment of 12 units',
'location_id': self.inventory_location.id,
'location_dest_id': self.stock_location.id,
'product_id': self.product1.id,
'product_uom': self.uom_unit.id,
'product_uom_qty': 12.0,
})
move1._action_confirm()
move1._action_assign()
move1.move_line_ids.qty_done = 12.0
move1._action_done()
self.assertAlmostEqual(move1.value, 180.0)
self.assertAlmostEqual(move1.remaining_qty, 12.0)
self.assertAlmostEqual(self.product1.stock_value, 180.0)
# Sell the 12 units
move2 = self.env['stock.move'].create({
'name': 'Sell 12 units',
'location_id': self.stock_location.id,
'location_dest_id': self.customer_location.id,
'product_id': self.product1.id,
'product_uom': self.uom_unit.id,
'product_uom_qty': 12.0,
})
move2._action_confirm()
move2._action_assign()
move2.move_line_ids.qty_done = 12.0
move2._action_done()
self.assertAlmostEqual(move1.remaining_qty, 0.0)
self.assertAlmostEqual(self.product1.stock_value, 0.0)
+1 -1
View File
@@ -20,7 +20,7 @@ class CustomerPortal(CustomerPortal):
except exceptions.AccessError:
pass
else:
if order_sudo.template_id and order_sudo.template_id.active:
if order_sudo.template_id and order_sudo.template_id.active and order_sudo.state in ['draft', 'sent']:
return request.redirect('/quote/%s/%s' % (order, access_token or ''))
return super(CustomerPortal, self).portal_order_page(order=order, access_token=access_token, **kw)
+21 -5
View File
@@ -98,11 +98,26 @@ class SaleOrder(models.Model):
'pricelist': order.pricelist_id.id,
})
product = self.env['product.product'].with_context(product_context).browse(product_id)
pu = product.price
if order.pricelist_id and order.partner_id:
order_line = order._cart_find_product_line(product.id)
if order_line:
pu = self.env['account.tax']._fix_tax_included_price_company(pu, product.taxes_id, order_line[0].tax_id, self.company_id)
discount = 0
if order.pricelist_id.discount_policy == 'without_discount':
# This part is pretty much a copy-paste of the method '_onchange_discount' of
# 'sale.order.line'.
price, rule_id = order.pricelist_id.with_context(product_context).get_product_price_rule(product, qty or 1.0, order.partner_id)
pu, currency_id = request.env['sale.order.line'].with_context(product_context)._get_real_price_currency(product, rule_id, qty, product.uom_id, order.pricelist_id.id)
if pu != 0:
if order.pricelist_id.currency_id.id != currency_id:
# we need new_list_price in the same currency as price, which is in the SO's pricelist's currency
pu = request.env['res.currency'].browse(currency_id).with_context(product_context).compute(pu, order.pricelist_id.currency_id)
discount = (pu - price) / pu * 100
if discount < 0:
discount = 0
else:
pu = product.price
if order.pricelist_id and order.partner_id:
order_line = order._cart_find_product_line(product.id)
if order_line:
pu = self.env['account.tax']._fix_tax_included_price_company(pu, product.taxes_id, order_line[0].tax_id, self.company_id)
return {
'product_id': product_id,
@@ -110,6 +125,7 @@ class SaleOrder(models.Model):
'order_id': order_id,
'product_uom': product.uom_id.id,
'price_unit': pu,
'discount': discount,
}
@api.multi
+2 -2
View File
@@ -57,7 +57,7 @@ class ResPartnerBank(models.Model):
_name = 'res.partner.bank'
_rec_name = 'acc_number'
_description = 'Bank Accounts'
_order = 'sequence'
_order = 'sequence, id'
acc_type = fields.Selection([('bank', 'Normal')], compute='_compute_acc_type', string='Type', help='Bank account type: Normal or IBAN. Inferred from the bank account number.')
acc_number = fields.Char('Account Number', required=True)
@@ -67,7 +67,7 @@ class ResPartnerBank(models.Model):
bank_id = fields.Many2one('res.bank', string='Bank')
bank_name = fields.Char(related='bank_id.name')
bank_bic = fields.Char(related='bank_id.bic')
sequence = fields.Integer()
sequence = fields.Integer(default=10)
currency_id = fields.Many2one('res.currency', string='Currency')
company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env.user.company_id, ondelete='cascade')
+2 -1
View File
@@ -64,6 +64,7 @@
<form string="Bank account">
<group>
<group>
<field name="sequence" invisible="1"/>
<field name="acc_number"/>
<field name="acc_type"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
@@ -83,7 +84,7 @@
<field name="model">res.partner.bank</field>
<field name="arch" type="xml">
<tree string="Bank Accounts">
<field name="sequence" invisible="1"/>
<field name="sequence" widget="handle"/>
<field name="acc_number"/>
<field name="bank_name"/>
<field name="company_id" groups="base.group_multi_company"/>