[MERGE] forward port branch 11.0 up to 4df568549a
This commit is contained in:
@@ -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,))
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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>
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
Reference in New Issue
Block a user