mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2025-02-25 18:55:21 -06:00
[PATCH] Upstream patch - 30052022
This commit is contained in:
@@ -7,4 +7,5 @@ from flectra import api, SUPERUSER_ID
|
||||
|
||||
def _update_street_format(cr, registry):
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
env['res.partner'].search([])._compute_street_data()
|
||||
specific_countries = env['res.country'].search([('street_format', '!=', '%(street_number)s/%(street_number2)s %(street_name)s')])
|
||||
env['res.partner'].search([('country_id', 'in', specific_countries.ids)])._compute_street_data()
|
||||
|
||||
@@ -6,6 +6,7 @@ const {
|
||||
afterEach,
|
||||
afterNextRender,
|
||||
beforeEach,
|
||||
isScrolledToBottom,
|
||||
nextAnimationFrame,
|
||||
start,
|
||||
} = require('mail/static/src/utils/test_utils.js');
|
||||
@@ -1028,7 +1029,7 @@ QUnit.test('[technical] chat window: scroll conservation on toggle home menu', a
|
||||
thread &&
|
||||
thread.model === 'mail.channel' &&
|
||||
thread.id === 20 &&
|
||||
scrollTop === messageList.scrollHeight - messageList.clientHeight
|
||||
isScrolledToBottom(messageList)
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -1799,7 +1800,7 @@ QUnit.test('chat window with a thread: keep scroll position in message list on f
|
||||
thread &&
|
||||
thread.model === 'mail.channel' &&
|
||||
thread.id === 20 &&
|
||||
scrollTop === messageList.scrollHeight - messageList.clientHeight
|
||||
isScrolledToBottom(messageList)
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -1885,9 +1886,8 @@ QUnit.test('chat window should scroll to the newly posted message just after pos
|
||||
)
|
||||
);
|
||||
const messageList = document.querySelector('.o_MessageList');
|
||||
assert.strictEqual(
|
||||
messageList.scrollHeight - messageList.scrollTop,
|
||||
messageList.clientHeight,
|
||||
assert.ok(
|
||||
isScrolledToBottom(messageList),
|
||||
"chat window should scroll to the newly posted message just after posting it"
|
||||
);
|
||||
});
|
||||
@@ -2038,7 +2038,7 @@ QUnit.test('[technical] chat window with a thread: keep scroll position in messa
|
||||
thread &&
|
||||
thread.model === 'mail.channel' &&
|
||||
thread.id === 20 &&
|
||||
scrollTop === messageList.scrollHeight - messageList.clientHeight
|
||||
isScrolledToBottom(messageList)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ const {
|
||||
afterEach,
|
||||
afterNextRender,
|
||||
beforeEach,
|
||||
isScrolledToBottom,
|
||||
nextAnimationFrame,
|
||||
start,
|
||||
} = require('mail/static/src/utils/test_utils.js');
|
||||
@@ -1715,7 +1716,7 @@ QUnit.test('auto-scroll to bottom of thread', async function (assert) {
|
||||
thread &&
|
||||
thread.model === 'mail.channel' &&
|
||||
thread.id === 20 &&
|
||||
scrollTop === messageList.scrollHeight - messageList.clientHeight
|
||||
isScrolledToBottom(messageList)
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -1728,9 +1729,8 @@ QUnit.test('auto-scroll to bottom of thread', async function (assert) {
|
||||
"should have 25 messages"
|
||||
);
|
||||
const messageList = document.querySelector(`.o_Discuss_thread .o_ThreadView_messageList`);
|
||||
assert.strictEqual(
|
||||
messageList.scrollTop,
|
||||
messageList.scrollHeight - messageList.clientHeight,
|
||||
assert.ok(
|
||||
isScrolledToBottom(messageList),
|
||||
"should have scrolled to bottom of thread"
|
||||
);
|
||||
});
|
||||
@@ -1763,7 +1763,7 @@ QUnit.test('load more messages from channel (auto-load on scroll)', async functi
|
||||
thread &&
|
||||
thread.model === 'mail.channel' &&
|
||||
thread.id === 20 &&
|
||||
scrollTop === messageList.scrollHeight - messageList.clientHeight
|
||||
isScrolledToBottom(messageList)
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -1852,7 +1852,7 @@ QUnit.test('new messages separator [REQUIRE FOCUS]', async function (assert) {
|
||||
thread &&
|
||||
thread.model === 'mail.channel' &&
|
||||
thread.id === 20 &&
|
||||
scrollTop === messageList.scrollHeight - messageList.clientHeight
|
||||
isScrolledToBottom(messageList)
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -1922,7 +1922,7 @@ QUnit.test('new messages separator [REQUIRE FOCUS]', async function (assert) {
|
||||
thread &&
|
||||
thread.model === 'mail.channel' &&
|
||||
thread.id === 20 &&
|
||||
scrollTop === messageList.scrollHeight - messageList.clientHeight
|
||||
isScrolledToBottom(messageList)
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -1994,9 +1994,8 @@ QUnit.test('restore thread scroll position', async function (assert) {
|
||||
.o_Discuss_thread
|
||||
.o_ThreadView_messageList
|
||||
`);
|
||||
assert.strictEqual(
|
||||
initialMessageList.scrollTop,
|
||||
initialMessageList.scrollHeight - initialMessageList.clientHeight,
|
||||
assert.ok(
|
||||
isScrolledToBottom(initialMessageList),
|
||||
"should have scrolled to bottom of channel 11 initially"
|
||||
);
|
||||
|
||||
@@ -2039,7 +2038,7 @@ QUnit.test('restore thread scroll position', async function (assert) {
|
||||
thread &&
|
||||
thread.model === 'mail.channel' &&
|
||||
thread.id === 12 &&
|
||||
scrollTop === messageList.scrollHeight - messageList.clientHeight
|
||||
isScrolledToBottom(messageList)
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -2102,14 +2101,13 @@ QUnit.test('restore thread scroll position', async function (assert) {
|
||||
thread &&
|
||||
thread.model === 'mail.channel' &&
|
||||
thread.id === 12 &&
|
||||
scrollTop === messageList.scrollHeight - messageList.clientHeight
|
||||
isScrolledToBottom(messageList)
|
||||
);
|
||||
},
|
||||
});
|
||||
const messageList = document.querySelector('.o_ThreadView_messageList');
|
||||
assert.strictEqual(
|
||||
messageList.scrollTop,
|
||||
messageList.scrollHeight - messageList.clientHeight,
|
||||
assert.ok(
|
||||
isScrolledToBottom(messageList),
|
||||
"should have recovered scroll position of channel 12 (scroll to bottom)"
|
||||
);
|
||||
});
|
||||
@@ -3687,7 +3685,7 @@ QUnit.test('load recent messages from thread (already loaded some old messages)'
|
||||
thread &&
|
||||
thread.model === 'mail.channel' &&
|
||||
thread.id === 20 &&
|
||||
scrollTop === messageList.scrollHeight - messageList.clientHeight
|
||||
isScrolledToBottom(messageList)
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -4018,7 +4016,7 @@ QUnit.test('all messages in "Inbox" in "History" after marked all as read', asyn
|
||||
thread.model === 'mail.box' &&
|
||||
thread.id === 'inbox' &&
|
||||
orderedMessages.length === 30 &&
|
||||
scrollTop === messageList.scrollHeight - messageList.clientHeight
|
||||
isScrolledToBottom(messageList)
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -4051,7 +4049,7 @@ QUnit.test('all messages in "Inbox" in "History" after marked all as read', asyn
|
||||
thread.model === 'mail.box' &&
|
||||
thread.id === 'history' &&
|
||||
orderedMessages.length === 30 &&
|
||||
scrollTop === messageList.scrollHeight - messageList.clientHeight
|
||||
isScrolledToBottom(messageList)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ const {
|
||||
beforeEach,
|
||||
createRootComponent,
|
||||
dragenterFiles,
|
||||
isScrolledToBottom,
|
||||
start,
|
||||
} = require('mail/static/src/utils/test_utils.js');
|
||||
|
||||
@@ -833,9 +834,8 @@ QUnit.test('should scroll to bottom on receiving new message if the list is init
|
||||
predicate: data => threadViewer === data.threadViewer,
|
||||
});
|
||||
const initialMessageList = document.querySelector('.o_ThreadView_messageList');
|
||||
assert.strictEqual(
|
||||
initialMessageList.scrollTop,
|
||||
initialMessageList.scrollHeight - initialMessageList.clientHeight,
|
||||
assert.ok(
|
||||
isScrolledToBottom(initialMessageList),
|
||||
"should have scrolled to bottom of channel 20 initially"
|
||||
);
|
||||
|
||||
@@ -857,9 +857,8 @@ QUnit.test('should scroll to bottom on receiving new message if the list is init
|
||||
predicate: data => threadViewer === data.threadViewer,
|
||||
});
|
||||
const messageList = document.querySelector('.o_ThreadView_messageList');
|
||||
assert.strictEqual(
|
||||
messageList.scrollTop,
|
||||
messageList.scrollHeight - messageList.clientHeight,
|
||||
assert.ok(
|
||||
isScrolledToBottom(messageList),
|
||||
"should scroll to bottom on receiving new message because the list is initially scrolled to bottom"
|
||||
);
|
||||
});
|
||||
@@ -904,9 +903,8 @@ QUnit.test('should not scroll on receiving new message if the list is initially
|
||||
predicate: data => threadViewer === data.threadViewer,
|
||||
});
|
||||
const initialMessageList = document.querySelector('.o_ThreadView_messageList');
|
||||
assert.strictEqual(
|
||||
initialMessageList.scrollTop,
|
||||
initialMessageList.scrollHeight - initialMessageList.clientHeight,
|
||||
assert.ok(
|
||||
isScrolledToBottom(initialMessageList),
|
||||
"should have scrolled to bottom of channel 20 initially"
|
||||
);
|
||||
|
||||
|
||||
@@ -747,6 +747,23 @@ function pasteFiles(el, files) {
|
||||
el.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public: DOM utilities
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Determine if a DOM element has been totally scrolled
|
||||
*
|
||||
* A 1px margin of error is given to accomodate subpixel rounding issues and
|
||||
* Element.scrollHeight value being either int or decimal
|
||||
*
|
||||
* @param {DOM.Element} el
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isScrolledToBottom(el) {
|
||||
return Math.abs(el.scrollHeight - el.clientHeight - el.scrollTop) <= 1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Export
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -758,6 +775,7 @@ return {
|
||||
createRootComponent,
|
||||
dragenterFiles,
|
||||
dropFiles,
|
||||
isScrolledToBottom,
|
||||
nextAnimationFrame,
|
||||
nextTick,
|
||||
pasteFiles,
|
||||
|
||||
@@ -6,6 +6,7 @@ const {
|
||||
afterEach,
|
||||
afterNextRender,
|
||||
beforeEach,
|
||||
isScrolledToBottom,
|
||||
nextAnimationFrame,
|
||||
start,
|
||||
} = require('mail/static/src/utils/test_utils.js');
|
||||
@@ -846,9 +847,8 @@ QUnit.test('Form view not scrolled when switching record', async function (asser
|
||||
'scroll'
|
||||
);
|
||||
});
|
||||
assert.strictEqual(
|
||||
controllerContentEl.scrollTop,
|
||||
controllerContentEl.scrollHeight - controllerContentEl.clientHeight,
|
||||
assert.ok(
|
||||
isScrolledToBottom(controllerContentEl),
|
||||
"The controller container should be scrolled to its bottom"
|
||||
);
|
||||
|
||||
|
||||
@@ -143,8 +143,9 @@ class MrpBom(models.Model):
|
||||
res = super().copy(default)
|
||||
for bom_line in res.bom_line_ids:
|
||||
if bom_line.operation_id:
|
||||
operation = res.operation_ids.filtered(lambda op: op.name == bom_line.operation_id.name and op.workcenter_id == bom_line.operation_id.workcenter_id)
|
||||
bom_line.operation_id = operation
|
||||
operation = res.operation_ids.filtered(lambda op: op._get_comparison_values() == bom_line.operation_id._get_comparison_values())
|
||||
# Two operations could have the same values so we take the first one
|
||||
bom_line.operation_id = operation[:1]
|
||||
return res
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -77,3 +77,9 @@ class MrpRoutingWorkcenter(models.Model):
|
||||
count_data = dict((item['operation_id'][0], item['operation_id_count']) for item in data)
|
||||
for operation in self:
|
||||
operation.workorder_count = count_data.get(operation.id, 0)
|
||||
|
||||
def _get_comparison_values(self):
|
||||
if not self:
|
||||
return False
|
||||
self.ensure_one()
|
||||
return tuple(self[key] for key in ('name', 'company_id', 'workcenter_id', 'time_mode', 'time_cycle_manual'))
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
from flectra import _, api, fields, models
|
||||
from flectra.tools.float_utils import float_is_zero
|
||||
|
||||
from flectra.osv.expression import AND
|
||||
|
||||
class StockWarehouseOrderpoint(models.Model):
|
||||
_inherit = 'stock.warehouse.orderpoint'
|
||||
@@ -13,12 +13,12 @@ class StockWarehouseOrderpoint(models.Model):
|
||||
'mrp.bom', string='Bill of Materials', check_company=True,
|
||||
domain="[('type', '=', 'normal'), '&', '|', ('company_id', '=', company_id), ('company_id', '=', False), '|', ('product_id', '=', product_id), '&', ('product_id', '=', False), ('product_tmpl_id', '=', product_tmpl_id)]")
|
||||
|
||||
def _get_replenishment_order_notification(self, written_after):
|
||||
def _get_replenishment_order_notification(self):
|
||||
self.ensure_one()
|
||||
production = self.env['mrp.production'].search([
|
||||
('orderpoint_id', 'in', self.ids),
|
||||
('write_date', '>', written_after)
|
||||
], order='create_date desc', limit=1)
|
||||
domain = [('orderpoint_id', 'in', self.ids)]
|
||||
if self.env.context.get('written_date'):
|
||||
domain = AND([domain, [('write_date', '>', self.env.context.get('written_after'))]])
|
||||
production = self.env['mrp.production'].search(domain, limit=1)
|
||||
if production:
|
||||
action = self.env.ref('mrp.action_mrp_production_form')
|
||||
return {
|
||||
@@ -34,7 +34,7 @@ class StockWarehouseOrderpoint(models.Model):
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return super()._get_replenishment_order_notification(written_after)
|
||||
return super()._get_replenishment_order_notification()
|
||||
|
||||
@api.depends('route_id')
|
||||
def _compute_show_bom(self):
|
||||
|
||||
@@ -794,7 +794,7 @@ class PosSession(models.Model):
|
||||
# may arise in 'Round Globally'.
|
||||
check_refund = lambda x: x.qty * x.price_unit < 0
|
||||
is_refund = check_refund(order_line)
|
||||
tax_data = tax_ids.compute_all(price_unit=price, quantity=abs(order_line.qty), currency=self.currency_id, is_refund=is_refund)
|
||||
tax_data = tax_ids.with_context(force_sign=sign).compute_all(price_unit=price, quantity=abs(order_line.qty), currency=self.currency_id, is_refund=is_refund)
|
||||
taxes = tax_data['taxes']
|
||||
# For Cash based taxes, use the account from the repartition line immediately as it has been paid already
|
||||
for tax in taxes:
|
||||
|
||||
@@ -6,6 +6,7 @@ flectra.define('point_of_sale.ClientListScreen', function(require) {
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const { isRpcError } = require('point_of_sale.utils');
|
||||
const { useAsyncLockedMethod } = require('point_of_sale.custom_hooks');
|
||||
|
||||
/**
|
||||
* Render this screen using `showTempScreen` to select client.
|
||||
@@ -25,9 +26,10 @@ flectra.define('point_of_sale.ClientListScreen', function(require) {
|
||||
class ClientListScreen extends PosComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.lockedSaveChanges = useAsyncLockedMethod(this.saveChanges);
|
||||
useListener('click-save', () => this.env.bus.trigger('save-customer'));
|
||||
useListener('click-edit', () => this.editClient());
|
||||
useListener('save-changes', this.saveChanges);
|
||||
useListener('save-changes', this.lockedSaveChanges);
|
||||
|
||||
// We are not using useState here because the object
|
||||
// passed to useState converts the object and its contents
|
||||
|
||||
@@ -3,7 +3,7 @@ flectra.define('point_of_sale.PaymentScreen', function (require) {
|
||||
|
||||
const { parse } = require('web.field_utils');
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const { useErrorHandlers } = require('point_of_sale.custom_hooks');
|
||||
const { useErrorHandlers, useAsyncLockedMethod } = require('point_of_sale.custom_hooks');
|
||||
const NumberBuffer = require('point_of_sale.NumberBuffer');
|
||||
const { useListener } = require('web.custom_hooks');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
@@ -20,6 +20,7 @@ flectra.define('point_of_sale.PaymentScreen', function (require) {
|
||||
useListener('send-payment-cancel', this._sendPaymentCancel);
|
||||
useListener('send-payment-reverse', this._sendPaymentReverse);
|
||||
useListener('send-force-done', this._sendForceDone);
|
||||
this.lockedValidateOrder = useAsyncLockedMethod(this.validateOrder);
|
||||
NumberBuffer.use({
|
||||
// The numberBuffer listens to this event to update its state.
|
||||
// Basically means 'update the buffer when this event is triggered'
|
||||
@@ -306,7 +307,7 @@ flectra.define('point_of_sale.PaymentScreen', function (require) {
|
||||
' ' +
|
||||
this.env._t('? Clicking "Confirm" will validate the payment.'),
|
||||
}).then(({ confirmed }) => {
|
||||
if (confirmed) this.validateOrder(true);
|
||||
if (confirmed) this.lockedValidateOrder(true);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -145,5 +145,21 @@ flectra.define('point_of_sale.custom_hooks', function (require) {
|
||||
});
|
||||
}
|
||||
|
||||
return { useErrorHandlers, useAutoFocusToLast, onChangeOrder, useBarcodeReader };
|
||||
function useAsyncLockedMethod(method) {
|
||||
const component = Component.current;
|
||||
let called = false;
|
||||
return async (...args) => {
|
||||
if (called) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
called = true;
|
||||
await method.call(component, ...args);
|
||||
} finally {
|
||||
called = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return { useErrorHandlers, useAutoFocusToLast, onChangeOrder, useBarcodeReader, useAsyncLockedMethod };
|
||||
});
|
||||
|
||||
@@ -1403,6 +1403,8 @@ exports.PosModel = Backbone.Model.extend({
|
||||
if(base < 0){
|
||||
base = -base;
|
||||
sign = -1;
|
||||
} else if(utils.float_is_zero(base, this.currency.decimals) && quantity < 0){
|
||||
sign = -1
|
||||
}
|
||||
|
||||
var total_included_checkpoints = {};
|
||||
@@ -2737,7 +2739,9 @@ exports.Order = Backbone.Model.extend({
|
||||
var orderlines = json.lines;
|
||||
for (var i = 0; i < orderlines.length; i++) {
|
||||
var orderline = orderlines[i][2];
|
||||
this.add_orderline(new exports.Orderline({}, {pos: this.pos, order: this, json: orderline}));
|
||||
if(this.pos.db.get_product_by_id(orderline.product_id)){
|
||||
this.add_orderline(new exports.Orderline({}, {pos: this.pos, order: this, json: orderline}));
|
||||
}
|
||||
}
|
||||
|
||||
var paymentlines = json.statement_ids;
|
||||
|
||||
@@ -103,3 +103,31 @@ flectra.define('point_of_sale.tour.ProductScreen', function (require) {
|
||||
|
||||
Tour.register('ProductScreenTour', { test: true, url: '/pos/ui' }, getSteps());
|
||||
});
|
||||
|
||||
flectra.define('point_of_sale.tour.FixedPriceNegativeQty', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods');
|
||||
const { PaymentScreen } = require('point_of_sale.tour.PaymentScreenTourMethods');
|
||||
const { ReceiptScreen } = require('point_of_sale.tour.ReceiptScreenTourMethods');
|
||||
const { getSteps, startSteps } = require('point_of_sale.tour.utils');
|
||||
var Tour = require('web_tour.tour');
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Zero Amount Product');
|
||||
ProductScreen.check.selectedOrderlineHas('Zero Amount Product', '1.0', '1.0');
|
||||
ProductScreen.do.pressNumpad('+/- 1');
|
||||
ProductScreen.check.selectedOrderlineHas('Zero Amount Product', '-1.0', '-1.0');
|
||||
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod('Bank');
|
||||
PaymentScreen.check.remainingIs('0.00');
|
||||
PaymentScreen.do.clickValidate();
|
||||
|
||||
ReceiptScreen.check.receiptIsThere();
|
||||
|
||||
Tour.register('FixedTaxNegativeQty', { test: true, url: '/pos/ui' }, getSteps());
|
||||
});
|
||||
|
||||
@@ -523,3 +523,60 @@ class TestUi(TestPointOfSaleHttpCommon):
|
||||
def test_05_ticket_screen(self):
|
||||
self.main_pos_config.open_session_cb(check_coa=False)
|
||||
self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'TicketScreenTour', login="admin")
|
||||
|
||||
def test_fixed_tax_negative_qty(self):
|
||||
""" Assert the negative amount of a negative-quantity orderline
|
||||
with zero-amount product with fixed tax.
|
||||
"""
|
||||
|
||||
# setup the zero-amount product
|
||||
tax_received_account = self.env['account.account'].create({
|
||||
'name': 'TAX_BASE',
|
||||
'code': 'TBASE',
|
||||
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
fixed_tax = self.env['account.tax'].create({
|
||||
'name': 'fixed amount tax',
|
||||
'amount_type': 'fixed',
|
||||
'amount': 1,
|
||||
'invoice_repartition_line_ids': [
|
||||
(0, 0, {
|
||||
'factor_percent': 100,
|
||||
'repartition_type': 'base',
|
||||
}),
|
||||
(0, 0, {
|
||||
'factor_percent': 100,
|
||||
'repartition_type': 'tax',
|
||||
'account_id': tax_received_account.id,
|
||||
}),
|
||||
],
|
||||
})
|
||||
zero_amount_product = self.env['product.product'].create({
|
||||
'name': 'Zero Amount Product',
|
||||
'available_in_pos': True,
|
||||
'list_price': 0,
|
||||
'taxes_id': [(6, 0, [fixed_tax.id])],
|
||||
})
|
||||
|
||||
# Make an order with the zero-amount product from the frontend.
|
||||
# We need to do this because of the fix in the "compute_all" port.
|
||||
self.main_pos_config.write({'iface_tax_included': 'total'})
|
||||
self.main_pos_config.open_session_cb(check_coa=False)
|
||||
self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'FixedTaxNegativeQty', login="admin")
|
||||
pos_session = self.main_pos_config.current_session_id
|
||||
|
||||
# Close the session and check the session journal entry.
|
||||
pos_session.action_pos_session_validate()
|
||||
|
||||
lines = pos_session.move_id.line_ids.sorted('balance')
|
||||
|
||||
# order in the tour is paid using the bank payment method.
|
||||
bank_pm = self.main_pos_config.payment_method_ids.filtered(lambda pm: pm.name == 'Bank')
|
||||
|
||||
self.assertEqual(lines[0].account_id, bank_pm.receivable_account_id)
|
||||
self.assertAlmostEqual(lines[0].balance, -1)
|
||||
self.assertEqual(lines[1].account_id, zero_amount_product.categ_id.property_account_income_categ_id)
|
||||
self.assertAlmostEqual(lines[1].balance, 0)
|
||||
self.assertEqual(lines[2].account_id, tax_received_account)
|
||||
self.assertAlmostEqual(lines[2].balance, 1)
|
||||
|
||||
@@ -576,3 +576,45 @@ class TestPoSProductsWithTax(TestPoSCommon):
|
||||
{'account_id': self.tax_received_account.id, 'balance': 15.14, 'tax_ids': [], 'tax_tag_ids': self.tax_tag_refund_tax.ids},
|
||||
{'account_id': self.sale_account.id, 'balance': 72.07, 'tax_ids': tax_21_incl.ids, 'tax_tag_ids': self.tax_tag_refund_base.ids},
|
||||
])
|
||||
|
||||
def test_fixed_tax_positive_qty(self):
|
||||
|
||||
fixed_tax = self.env['account.tax'].create({
|
||||
'name': 'fixed amount tax',
|
||||
'amount_type': 'fixed',
|
||||
'amount': 1,
|
||||
'invoice_repartition_line_ids': [
|
||||
(0, 0, {
|
||||
'factor_percent': 100,
|
||||
'repartition_type': 'base',
|
||||
'tag_ids': [(6, 0, self.tax_tag_invoice_base.ids)],
|
||||
}),
|
||||
(0, 0, {
|
||||
'factor_percent': 100,
|
||||
'repartition_type': 'tax',
|
||||
'account_id': self.tax_received_account.id,
|
||||
'tag_ids': [(6, 0, self.tax_tag_invoice_tax.ids)],
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
zero_amount_product = self.env['product.product'].create({
|
||||
'name': 'Zero Amount Product',
|
||||
'available_in_pos': True,
|
||||
'list_price': 0,
|
||||
'taxes_id': [(6, 0, [fixed_tax.id])],
|
||||
})
|
||||
|
||||
self.open_new_session()
|
||||
self.env['pos.order'].create_from_ui([self.create_ui_order_data([
|
||||
(zero_amount_product, 1),
|
||||
])])
|
||||
self.pos_session.action_pos_session_validate()
|
||||
|
||||
lines = self.pos_session.move_id.line_ids.sorted('balance')
|
||||
|
||||
self.assertRecordValues(lines, [
|
||||
{'account_id': self.tax_received_account.id, 'balance': -1},
|
||||
{'account_id': self.sale_account.id, 'balance': 0},
|
||||
{'account_id': self.pos_receivable_account.id, 'balance': 1},
|
||||
])
|
||||
|
||||
@@ -32,8 +32,8 @@ class SaleReport(models.Model):
|
||||
CASE WHEN pos.state != 'invoiced' THEN sum(l.qty) ELSE 0 END AS qty_to_invoice,
|
||||
SUM(l.price_subtotal_incl) / MIN(CASE COALESCE(pos.currency_rate, 0) WHEN 0 THEN 1.0 ELSE pos.currency_rate END) AS price_total,
|
||||
SUM(l.price_subtotal) / MIN(CASE COALESCE(pos.currency_rate, 0) WHEN 0 THEN 1.0 ELSE pos.currency_rate END) AS price_subtotal,
|
||||
(CASE WHEN pos.state != 'invoiced' THEN SUM(l.price_subtotal_incl) ELSE 0 END) / MIN(CASE COALESCE(pos.currency_rate, 0) WHEN 0 THEN 1.0 ELSE pos.currency_rate END) AS amount_to_invoice,
|
||||
(CASE WHEN pos.state = 'invoiced' THEN SUM(l.price_subtotal_incl) ELSE 0 END) / MIN(CASE COALESCE(pos.currency_rate, 0) WHEN 0 THEN 1.0 ELSE pos.currency_rate END) AS amount_invoiced,
|
||||
(CASE WHEN pos.state != 'invoiced' THEN SUM(l.price_subtotal) ELSE 0 END) / MIN(CASE COALESCE(pos.currency_rate, 0) WHEN 0 THEN 1.0 ELSE pos.currency_rate END) AS amount_to_invoice,
|
||||
(CASE WHEN pos.state = 'invoiced' THEN SUM(l.price_subtotal) ELSE 0 END) / MIN(CASE COALESCE(pos.currency_rate, 0) WHEN 0 THEN 1.0 ELSE pos.currency_rate END) AS amount_invoiced,
|
||||
count(*) AS nbr,
|
||||
pos.name AS name,
|
||||
pos.date_order AS date,
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
<field name="date_start" optional="show"/>
|
||||
<field name="date_end" optional="show"/>
|
||||
<field name="applied_on" invisible="1"/>
|
||||
<field name="company_id" groups="base.group_multi_company" optional="show" options="{'no_create_edit':1, 'no_open': 1}"/>
|
||||
<field name="company_id" groups="base.group_multi_company" optional="show" options="{'no_create':1, 'no_open': 1}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<field name="name" string="Product Name"/>
|
||||
<field name="default_code" optional="show"/>
|
||||
<field name="barcode" optional="hide" attrs="{'readonly': [('product_variant_count', '>', 1)]}"/>
|
||||
<field name="company_id" options="{'no_create_edit': True}"
|
||||
<field name="company_id" options="{'no_create': True}"
|
||||
groups="base.group_multi_company" optional="hide"/>
|
||||
<field name="list_price" string="Sales Price" widget='monetary' options="{'currency_field': 'currency_id'}" optional="show" decoration-muted="not sale_ok"/>
|
||||
<field name="standard_price" widget='monetary' options="{'currency_field': 'cost_currency_id'}" optional="show" readonly="1"/>
|
||||
|
||||
@@ -250,7 +250,7 @@ QUnit.module('section_and_note: purchase_product_matrix', {
|
||||
// move first row below second
|
||||
const $firstHandle = form.$('.o_data_row:nth(0) .o_row_handle');
|
||||
const $secondHandle = form.$('.o_data_row:nth(1) .o_row_handle');
|
||||
await testUtils.dom.dragAndDrop($firstHandle, $secondHandle);
|
||||
await testUtils.dom.dragAndDrop($firstHandle, $secondHandle, { position: 'bottom' });
|
||||
|
||||
assert.strictEqual(form.$('.o_data_row').text(), 'TableChair');
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from flectra import api, fields, models, _
|
||||
from flectra.tools.float_utils import float_round, float_is_zero
|
||||
from flectra.exceptions import UserError
|
||||
|
||||
from flectra.osv.expression import AND
|
||||
|
||||
class StockPicking(models.Model):
|
||||
_inherit = 'stock.picking'
|
||||
@@ -237,12 +237,12 @@ class Orderpoint(models.Model):
|
||||
|
||||
return result
|
||||
|
||||
def _get_replenishment_order_notification(self, written_after):
|
||||
def _get_replenishment_order_notification(self):
|
||||
self.ensure_one()
|
||||
order = self.env['purchase.order.line'].search([
|
||||
('orderpoint_id', 'in', self.ids),
|
||||
('write_date', '>', written_after)
|
||||
], limit=1).order_id
|
||||
domain = [('orderpoint_id', 'in', self.ids)]
|
||||
if self.env.context.get('written_date'):
|
||||
domain = AND([domain, [('write_date', '>', self.env.context.get('written_after'))]])
|
||||
order = self.env['purchase.order.line'].search(domain, limit=1).order_id
|
||||
if order:
|
||||
action = self.env.ref('purchase.action_rfq_form')
|
||||
return {
|
||||
@@ -258,7 +258,7 @@ class Orderpoint(models.Model):
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
return super()._get_replenishment_order_notification(written_after)
|
||||
return super()._get_replenishment_order_notification()
|
||||
|
||||
def _prepare_procurement_values(self, date=False, group=False):
|
||||
values = super()._prepare_procurement_values(date=date, group=group)
|
||||
|
||||
@@ -90,7 +90,7 @@ flectra.define('sale.product_configurator_tests', function (require) {
|
||||
// move first row below second
|
||||
const $firstHandle = form.$('.o_data_row:nth(0) .o_row_handle');
|
||||
const $secondHandle = form.$('.o_data_row:nth(1) .o_row_handle');
|
||||
await testUtils.dom.dragAndDrop($firstHandle, $secondHandle);
|
||||
await testUtils.dom.dragAndDrop($firstHandle, $secondHandle, { position: 'bottom' });
|
||||
|
||||
assert.strictEqual(form.$('.o_data_row').text(), 'TableChair');
|
||||
|
||||
|
||||
@@ -298,7 +298,7 @@ QUnit.module('Product Configurator', {
|
||||
// move first row below second
|
||||
const $firstHandle = form.$('.o_data_row:nth(0) .o_row_handle');
|
||||
const $secondHandle = form.$('.o_data_row:nth(1) .o_row_handle');
|
||||
await testUtils.dom.dragAndDrop($firstHandle, $secondHandle);
|
||||
await testUtils.dom.dragAndDrop($firstHandle, $secondHandle, { position: 'bottom' });
|
||||
|
||||
assert.strictEqual(form.$('.o_data_row').text(), 'Customizable Desk (2)Customizable Desk (1)');
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ class StockWarehouseOrderpoint(models.Model):
|
||||
self._procure_orderpoint_confirm(company_id=self.env.company)
|
||||
notification = False
|
||||
if len(self) == 1:
|
||||
notification = self._get_replenishment_order_notification(now)
|
||||
notification = self.with_context(written_after=now)._get_replenishment_order_notification()
|
||||
# Forced to call compute quantity because we don't have a link.
|
||||
self._compute_qty()
|
||||
self.filtered(lambda o: o.create_uid.id == SUPERUSER_ID and o.qty_to_order <= 0.0 and o.trigger == 'manual').unlink()
|
||||
@@ -425,12 +425,12 @@ class StockWarehouseOrderpoint(models.Model):
|
||||
'trigger': 'manual',
|
||||
}
|
||||
|
||||
def _get_replenishment_order_notification(self, written_after):
|
||||
def _get_replenishment_order_notification(self):
|
||||
self.ensure_one()
|
||||
move = self.env['stock.move'].search([
|
||||
('orderpoint_id', 'in', self.ids),
|
||||
('write_date', '>', written_after)
|
||||
], limit=1)
|
||||
domain = [('orderpoint_id', 'in', self.ids)]
|
||||
if self.env.context.get('written_date'):
|
||||
domain = expression.AND([domain, [('write_date', '>', self.env.context.get('written_after'))]])
|
||||
move = self.env['stock.move'].search(domain, limit=1)
|
||||
if move.picking_id:
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
|
||||
@@ -36,7 +36,9 @@ flectra.define('web.FavoriteMenu', function (require) {
|
||||
get icon() {
|
||||
return FACET_ICONS.favorite;
|
||||
}
|
||||
|
||||
get dialogTitle() {
|
||||
return this.env._t("Warning");
|
||||
}
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
</xpath>
|
||||
<xpath expr="//div[1]" position="inside">
|
||||
<Dialog t-if="state.deletedFavorite"
|
||||
title="'Warning'"
|
||||
title="dialogTitle"
|
||||
size="'medium'"
|
||||
t-on-dialog-closed="state.deletedFavorite = false"
|
||||
>
|
||||
|
||||
@@ -2627,8 +2627,8 @@ QUnit.module('Views', {
|
||||
|
||||
assert.strictEqual(list.$('table').width(), list.$('.o_list_view').width());
|
||||
const largeCells = list.$('.o_data_cell.large');
|
||||
assert.strictEqual(largeCells[0].offsetWidth, largeCells[1].offsetWidth);
|
||||
assert.strictEqual(largeCells[1].offsetWidth, largeCells[2].offsetWidth);
|
||||
assert.ok(Math.abs(largeCells[0].offsetWidth - largeCells[1].offsetWidth) <= 1);
|
||||
assert.ok(Math.abs(largeCells[1].offsetWidth - largeCells[2].offsetWidth) <= 1);
|
||||
assert.ok(list.$('.o_data_cell:not(.large)')[0].offsetWidth < largeCells[0].offsetWidth);
|
||||
|
||||
list.destroy();
|
||||
@@ -3119,7 +3119,7 @@ QUnit.module('Views', {
|
||||
const text = list.el.querySelector('th[data-name="text"]');
|
||||
const textWidth = Math.ceil(text.getBoundingClientRect().width);
|
||||
|
||||
assert.strictEqual(fooWidth, textWidth, "both columns should have been given the same width");
|
||||
assert.ok(Math.abs(fooWidth - textWidth) <= 1, "both columns should have been given the same width");
|
||||
|
||||
const firstRowHeight = list.$('.o_data_row:nth(0)')[0].offsetHeight;
|
||||
const secondRowHeight = list.$('.o_data_row:nth(1)')[0].offsetHeight;
|
||||
|
||||
Reference in New Issue
Block a user