mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2026-08-17 16:54:42 -05:00
[PATCH] Upstream patch - 14062023
This commit is contained in:
@@ -130,12 +130,12 @@ class Task(models.Model):
|
||||
def _compute_effective_hours(self):
|
||||
if not any(self._ids):
|
||||
for task in self:
|
||||
task.effective_hours = round(sum(task.timesheet_ids.mapped('unit_amount')), 2)
|
||||
task.effective_hours = sum(task.timesheet_ids.mapped('unit_amount'))
|
||||
return
|
||||
timesheet_read_group = self.env['account.analytic.line'].read_group([('task_id', 'in', self.ids)], ['unit_amount', 'task_id'], ['task_id'])
|
||||
timesheets_per_task = {res['task_id'][0]: res['unit_amount'] for res in timesheet_read_group}
|
||||
for task in self:
|
||||
task.effective_hours = round(timesheets_per_task.get(task.id, 0.0), 2)
|
||||
task.effective_hours = timesheets_per_task.get(task.id, 0.0)
|
||||
|
||||
@api.depends('effective_hours', 'subtask_effective_hours', 'planned_hours')
|
||||
def _compute_progress_hours(self):
|
||||
|
||||
@@ -499,3 +499,31 @@ class TestTimesheet(TestCommonTimesheet):
|
||||
self.assertEqual(timesheet.product_uom_id, self.project_customer.company_id.project_time_mode_id,
|
||||
"The product_uom_id of the timesheet should be equal to the project's company uom "
|
||||
"if the project's analytic account has no company_id")
|
||||
|
||||
def test_percentage_of_planned_hours(self):
|
||||
""" Test the percentage of planned hours on a task. """
|
||||
self.task1.planned_hours = round(11/60, 2)
|
||||
self.assertEqual(self.task1.effective_hours, 0, 'No timesheet should be created yet.')
|
||||
self.assertEqual(self.task1.progress, 0, 'No timesheet should be created yet.')
|
||||
self.env['account.analytic.line'].create([
|
||||
{
|
||||
'name': 'Timesheet',
|
||||
'project_id': self.project_customer.id,
|
||||
'task_id': self.task1.id,
|
||||
'unit_amount': 3/60,
|
||||
'employee_id': self.empl_employee.id,
|
||||
}, {
|
||||
'name': 'Timesheet',
|
||||
'project_id': self.project_customer.id,
|
||||
'task_id': self.task1.id,
|
||||
'unit_amount': 4/60,
|
||||
'employee_id': self.empl_employee.id,
|
||||
}, {
|
||||
'name': 'Timesheet',
|
||||
'project_id': self.project_customer.id,
|
||||
'task_id': self.task1.id,
|
||||
'unit_amount': 4/60,
|
||||
'employee_id': self.empl_employee.id,
|
||||
},
|
||||
])
|
||||
self.assertEqual(self.task1.progress, 100, 'The percentage of planned hours should be 100%.')
|
||||
|
||||
@@ -3404,6 +3404,39 @@
|
||||
}),
|
||||
]"/>
|
||||
</record>
|
||||
<record id="account_tax_template_p_irpf24_rdc" model="account.tax.template">
|
||||
<field name="type_tax_use">purchase</field>
|
||||
<field name="name">Retenciones IRPF 24% (Rendimientos del capital)</field>
|
||||
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
|
||||
<field name="amount" eval="-24"/>
|
||||
<field name="amount_type">percent</field>
|
||||
<field name="tax_group_id" ref="tax_group_retenciones_24"/>
|
||||
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
|
||||
(0,0, {
|
||||
'factor_percent': 100,
|
||||
'repartition_type': 'base',
|
||||
}),
|
||||
|
||||
(0,0, {
|
||||
'factor_percent': 100,
|
||||
'repartition_type': 'tax',
|
||||
'account_id': ref('l10n_es.account_common_4751'),
|
||||
}),
|
||||
|
||||
]"/>
|
||||
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
|
||||
(0,0, {
|
||||
'factor_percent': 100,
|
||||
'repartition_type': 'base',
|
||||
}),
|
||||
|
||||
(0,0, {
|
||||
'factor_percent': 100,
|
||||
'repartition_type': 'tax',
|
||||
'account_id': ref('l10n_es.account_common_4751'),
|
||||
}),
|
||||
]"/>
|
||||
</record>
|
||||
<record id="account_tax_template_s_irpf20" model="account.tax.template">
|
||||
<field name="description">Retención 20%</field>
|
||||
<field name="type_tax_use">sale</field>
|
||||
@@ -3506,6 +3539,40 @@
|
||||
}),
|
||||
]"/>
|
||||
</record>
|
||||
<record id="account_tax_template_s_irpf24_rdc" model="account.tax.template">
|
||||
<field name="description">Retención 24% (Rendimientos del capital)</field>
|
||||
<field name="type_tax_use">sale</field>
|
||||
<field name="name">Retenciones a cuenta IRPF 24%</field>
|
||||
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
|
||||
<field name="amount" eval="-24"/>
|
||||
<field name="amount_type">percent</field>
|
||||
<field name="tax_group_id" ref="tax_group_retenciones_24"/>
|
||||
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
|
||||
(0,0, {
|
||||
'factor_percent': 100,
|
||||
'repartition_type': 'base',
|
||||
}),
|
||||
|
||||
(0,0, {
|
||||
'factor_percent': 100,
|
||||
'repartition_type': 'tax',
|
||||
'account_id': ref('l10n_es.account_common_473'),
|
||||
}),
|
||||
|
||||
]"/>
|
||||
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
|
||||
(0,0, {
|
||||
'factor_percent': 100,
|
||||
'repartition_type': 'base',
|
||||
}),
|
||||
|
||||
(0,0, {
|
||||
'factor_percent': 100,
|
||||
'repartition_type': 'tax',
|
||||
'account_id': ref('l10n_es.account_common_473'),
|
||||
}),
|
||||
]"/>
|
||||
</record>
|
||||
<record id="account_tax_template_p_iva12_agr" model="account.tax.template">
|
||||
<field name="description"/> <!-- for resetting the value on existing DBs -->
|
||||
<field name="type_tax_use">purchase</field>
|
||||
|
||||
@@ -21,6 +21,7 @@ flectra.define('point_of_sale.ProductScreen', function(require) {
|
||||
useListener('click-pay', this._onClickPay);
|
||||
useBarcodeReader({
|
||||
product: this._barcodeProductAction,
|
||||
quantity: this._barcodeProductAction,
|
||||
weight: this._barcodeProductAction,
|
||||
price: this._barcodeProductAction,
|
||||
client: this._barcodeClientAction,
|
||||
@@ -221,7 +222,7 @@ flectra.define('point_of_sale.ProductScreen', function(require) {
|
||||
price_manually_set: true,
|
||||
},
|
||||
});
|
||||
} else if (code.type === 'weight') {
|
||||
} else if (code.type === 'weight' || code.type === 'quantity') {
|
||||
Object.assign(options, {
|
||||
quantity: code.value,
|
||||
merge: false,
|
||||
|
||||
@@ -5,7 +5,7 @@ from collections import defaultdict
|
||||
|
||||
from flectra import api, fields, models, _
|
||||
from flectra.exceptions import UserError
|
||||
from flectra.tools import float_is_zero, OrderedSet
|
||||
from flectra.tools import float_compare, float_is_zero, OrderedSet
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -40,6 +40,10 @@ class StockMove(models.Model):
|
||||
# If the move is a return, use the original move's price unit.
|
||||
if self.origin_returned_move_id and self.origin_returned_move_id.sudo().stock_valuation_layer_ids:
|
||||
layers = self.origin_returned_move_id.sudo().stock_valuation_layer_ids
|
||||
# dropshipping create additional positive svl to make sure there is no impact on the stock valuation
|
||||
# We need to remove them from the computation of the price unit.
|
||||
if self.origin_returned_move_id._is_dropshipped():
|
||||
layers = layers.filtered(lambda l: float_compare(l.value, 0, precision_rounding=l.product_id.uom_id.rounding) <= 0)
|
||||
layers |= layers.stock_valuation_layer_ids
|
||||
quantity = sum(layers.mapped("quantity"))
|
||||
return layers.currency_id.round(sum(layers.mapped("value")) / quantity) if not float_is_zero(quantity, precision_rounding=layers.uom_id.rounding) else 0
|
||||
|
||||
@@ -298,3 +298,29 @@ class TestStockValuation(ValuationReconciliationTestCommon):
|
||||
}
|
||||
|
||||
self._check_results(expected_aml, 4, all_amls_return - all_amls)
|
||||
|
||||
def test_dropship_fifo_return(self):
|
||||
"""Test the return of a dropship order with a product set to FIFO costing
|
||||
method. The unit price is correctly computed on the return picking svl.
|
||||
"""
|
||||
self.env.company.anglo_saxon_accounting = True
|
||||
self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
|
||||
self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
|
||||
self.product1.product_tmpl_id.invoice_policy = 'order'
|
||||
|
||||
self._dropship_product1()
|
||||
self.assertTrue(8 in self.purchase_order1.picking_ids.move_lines.stock_valuation_layer_ids.mapped('value'))
|
||||
self.assertTrue(-8 in self.purchase_order1.picking_ids.move_lines.stock_valuation_layer_ids.mapped('value'))
|
||||
|
||||
# return what we've done
|
||||
stock_return_picking_form = Form(self.env['stock.return.picking']
|
||||
.with_context(active_ids=self.sale_order1.picking_ids.ids, active_id=self.sale_order1.picking_ids.ids[0],
|
||||
active_model='stock.picking'))
|
||||
stock_return_picking = stock_return_picking_form.save()
|
||||
stock_return_picking_action = stock_return_picking.create_returns()
|
||||
return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
|
||||
return_pick.move_lines[0].move_line_ids[0].qty_done = 1.0
|
||||
return_pick._action_done()
|
||||
|
||||
self.assertTrue(8 in return_pick.move_lines.stock_valuation_layer_ids.mapped('value'))
|
||||
self.assertTrue(-8 in return_pick.move_lines.stock_valuation_layer_ids.mapped('value'))
|
||||
|
||||
@@ -120,9 +120,12 @@ class TestMultiCompanySetup(TestMailCommon, TestRecipients):
|
||||
)
|
||||
test_records[0].activity_schedule("test_mail.mail_act_test_todo", user_id=user_admin.id)
|
||||
test_records[1].activity_schedule("test_mail.mail_act_test_todo", user_id=user_admin.id)
|
||||
res_all = user_admin.systray_get_activities()
|
||||
test_activity = next(
|
||||
a for a in user_admin.systray_get_activities()
|
||||
if a['model'] == 'mail.test.multi.company.with.activity'
|
||||
)
|
||||
self.assertEqual(
|
||||
res_all[0],
|
||||
test_activity,
|
||||
{
|
||||
"actions": [{"icon": "fa-clock-o", "name": "Summary"}],
|
||||
"icon": "/base/static/description/icon.png",
|
||||
@@ -135,9 +138,13 @@ class TestMultiCompanySetup(TestMailCommon, TestRecipients):
|
||||
"type": "activity",
|
||||
}
|
||||
)
|
||||
res_c2 = user_admin.with_context(allowed_company_ids=[self.company_2.id]).systray_get_activities()
|
||||
|
||||
test_activity = next(
|
||||
a for a in user_admin.with_context(allowed_company_ids=[self.company_2.id]).systray_get_activities()
|
||||
if a['model'] == 'mail.test.multi.company.with.activity'
|
||||
)
|
||||
self.assertEqual(
|
||||
res_c2[0],
|
||||
test_activity,
|
||||
{
|
||||
"actions": [{"icon": "fa-clock-o", "name": "Summary"}],
|
||||
"icon": "/base/static/description/icon.png",
|
||||
|
||||
@@ -13,7 +13,7 @@ const TableOfContent = publicWidget.Widget.extend({
|
||||
*/
|
||||
async start() {
|
||||
await this._super(...arguments);
|
||||
this.$scrollingElement = $().getScrollingElement();
|
||||
this.$scrollingElement = this.$target.closest(".s_table_of_content").closestScrollable();
|
||||
this.previousPosition = -1;
|
||||
this._updateTableOfContentNavbarPosition();
|
||||
extraMenuUpdateCallbacks.push(this._updateTableOfContentNavbarPosition.bind(this));
|
||||
|
||||
@@ -134,7 +134,7 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
|
||||
toolbar.push(['history', ['undo', 'redo']]);
|
||||
|
||||
var options = {
|
||||
height: 200,
|
||||
height: 350,
|
||||
minHeight: 80,
|
||||
toolbar: toolbar,
|
||||
styleWithSpan: false,
|
||||
@@ -202,7 +202,10 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
|
||||
let $title = $form.find('input[name=post_name]');
|
||||
let $textarea = $form.find('textarea[name=content]');
|
||||
// It's not really in the textarea that the user write at first
|
||||
let textareaContent = $form.find('.o_wysiwyg_wrapper .note-editable.panel-body').text().trim();
|
||||
const fillableTextAreaEl = ev.currentTarget
|
||||
.querySelector(".o_wysiwyg_wrapper .note-editable.panel-body");
|
||||
const isTextAreaFilled = fillableTextAreaEl &&
|
||||
(fillableTextAreaEl.innerText.trim() || fillableTextAreaEl.querySelector("img"));
|
||||
|
||||
if ($title.length && $title[0].required) {
|
||||
if ($title.val()) {
|
||||
@@ -216,7 +219,7 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
|
||||
// Because the textarea is hidden, we add the red or green border to its container
|
||||
if ($textarea[0] && $textarea[0].required) {
|
||||
let $textareaContainer = $form.find('.o_wysiwyg_wrapper .note-editor.panel.panel-default');
|
||||
if (!textareaContent.length) {
|
||||
if (!isTextAreaFilled) {
|
||||
$textareaContainer.addClass('border border-danger rounded-top');
|
||||
validForm = false;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user