mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2026-10-25 07:27:39 -05:00
[PATCH] Upstream patch - 17112022
This commit is contained in:
1 parent
984dc6d3f5
commit
4b67769126
7 files changed
+165
-58
No files matched your search
@@ -43,60 +43,41 @@ class Partner(models.Model):
|
||||
'member_lines.account_invoice_line.move_id.partner_id',
|
||||
'free_member',
|
||||
'member_lines.date_to', 'member_lines.date_from',
|
||||
'associate_member')
|
||||
'associate_member', 'associate_member.membership_state')
|
||||
def _compute_membership_state(self):
|
||||
today = fields.Date.today()
|
||||
for partner in self:
|
||||
state = 'none'
|
||||
|
||||
partner.membership_start = self.env['membership.membership_line'].search([
|
||||
('partner', '=', partner.associate_member.id or partner.id), ('date_cancel','=',False)
|
||||
('partner', '=', partner.associate_member.id or partner.id), ('date_cancel', '=', False)
|
||||
], limit=1, order='date_from').date_from
|
||||
partner.membership_stop = self.env['membership.membership_line'].search([
|
||||
('partner', '=', partner.associate_member.id or partner.id),('date_cancel','=',False)
|
||||
('partner', '=', partner.associate_member.id or partner.id), ('date_cancel', '=', False)
|
||||
], limit=1, order='date_to desc').date_to
|
||||
partner.membership_cancel = self.env['membership.membership_line'].search([
|
||||
('partner', '=', partner.id)
|
||||
], limit=1, order='date_cancel').date_cancel
|
||||
|
||||
if partner.membership_cancel and today > partner.membership_cancel:
|
||||
partner.membership_state = 'free' if partner.free_member else 'canceled'
|
||||
continue
|
||||
if partner.membership_stop and today > partner.membership_stop:
|
||||
if partner.free_member:
|
||||
partner.membership_state = 'free'
|
||||
continue
|
||||
if partner.associate_member:
|
||||
partner.associate_member._compute_membership_state()
|
||||
partner.membership_state = partner.associate_member.membership_state
|
||||
continue
|
||||
|
||||
line_states = [mline.state for mline in partner.member_lines if \
|
||||
(mline.date_to or date.min) >= today and \
|
||||
(mline.date_from or date.min) <= today and \
|
||||
mline.account_invoice_line.move_id.partner_id == partner]
|
||||
if partner.free_member and partner.membership_state != 'paid':
|
||||
partner.membership_state = 'free'
|
||||
continue
|
||||
|
||||
if 'paid' in line_states:
|
||||
state = 'paid'
|
||||
elif 'invoiced' in line_states:
|
||||
state = 'invoiced'
|
||||
elif 'waiting' in line_states:
|
||||
state = 'waiting'
|
||||
elif 'canceled' in line_states:
|
||||
state = 'canceled'
|
||||
|
||||
if state == 'none':
|
||||
for mline in partner.member_lines:
|
||||
# if there is an old invoice paid, set the state to 'old'
|
||||
if ((mline.date_from or date.min) < today and (mline.date_to or date.min) < today and \
|
||||
(mline.date_from or date.min) <= (mline.date_to or date.min) and \
|
||||
mline.account_invoice_id and mline.account_invoice_id.payment_state in ('in_payment', 'paid')):
|
||||
state = 'old'
|
||||
break
|
||||
|
||||
if partner.free_member and state != 'paid':
|
||||
state = 'free'
|
||||
partner.membership_state = state
|
||||
for mline in partner.member_lines:
|
||||
if (mline.date_to or date.min) >= today and (mline.date_from or date.min) <= today:
|
||||
partner.membership_state = mline.state
|
||||
break
|
||||
elif ((mline.date_from or date.min) < today and (mline.date_to or date.min) <= today and \
|
||||
(mline.date_from or date.min) < (mline.date_to or date.min)):
|
||||
if mline.account_invoice_id and mline.account_invoice_id.payment_state in ('in_payment', 'paid'):
|
||||
partner.membership_state = 'old'
|
||||
elif mline.account_invoice_id and mline.account_invoice_id.state == 'cancel':
|
||||
partner.membership_state = 'canceled'
|
||||
break
|
||||
else:
|
||||
partner.membership_state = 'none'
|
||||
|
||||
@api.constrains('associate_member')
|
||||
def _check_recursion_associate_member(self):
|
||||
|
||||
@@ -693,20 +693,28 @@ class MrpProduction(models.Model):
|
||||
|
||||
@api.onchange('lot_producing_id')
|
||||
def _onchange_lot_producing(self):
|
||||
if self.product_id.tracking == 'serial':
|
||||
res = self._can_produce_serial_number()
|
||||
if res is not True:
|
||||
return res
|
||||
|
||||
def _can_produce_serial_number(self, sn=None):
|
||||
self.ensure_one()
|
||||
sn = sn or self.lot_producing_id
|
||||
if self.product_id.tracking == 'serial' and sn:
|
||||
if self.env['stock.move.line'].search([
|
||||
('lot_id', '=', self.lot_producing_id.id),
|
||||
('lot_id', '=', sn.id),
|
||||
('qty_done', '=', 1),
|
||||
('state', '=', 'done'),
|
||||
('location_id.usage', '!=', 'production'),
|
||||
('location_dest_id.usage', '!=', 'production'),
|
||||
], limit=1) or self._is_finished_sn_already_produced(self.lot_producing_id):
|
||||
], limit=1) or self._is_finished_sn_already_produced(sn):
|
||||
return {
|
||||
'warning': {
|
||||
'title': _('Warning'),
|
||||
'message': _('Existing Serial number (%s). Please correct the serial numbers encoded.') % self.lot_producing_id.name
|
||||
'message': _('Serial number (%s) has already been produced.') % sn.name,
|
||||
}
|
||||
}
|
||||
return True
|
||||
|
||||
@api.onchange('bom_id')
|
||||
def _onchange_workorder_ids(self):
|
||||
@@ -1787,8 +1795,14 @@ class MrpProduction(models.Model):
|
||||
('state', '=', 'done'),
|
||||
('location_dest_id.scrap_location', '=', True)
|
||||
])
|
||||
unremoved = self.env['stock.move.line'].search_count([
|
||||
('lot_id', '=', move_line.lot_id.id),
|
||||
('state', '=', 'done'),
|
||||
('location_id.scrap_location', '=', True),
|
||||
('location_dest_id.scrap_location', '=', False),
|
||||
])
|
||||
# Either removed or unbuild
|
||||
if not ((duplicates_returned or removed) and duplicates - duplicates_returned - removed == 0):
|
||||
if not ((duplicates_returned or removed) and duplicates - duplicates_returned - removed + unremoved == 0):
|
||||
raise UserError(message)
|
||||
# Check presence of same sn in current production
|
||||
duplicates = co_prod_move_lines.filtered(lambda ml: ml.qty_done and ml.lot_id == move_line.lot_id) - move_line
|
||||
|
||||
@@ -407,6 +407,12 @@ class MrpWorkorder(models.Model):
|
||||
def _onchange_expected_duration(self):
|
||||
self.duration_expected = self._get_duration_expected()
|
||||
|
||||
@api.onchange('finished_lot_id')
|
||||
def _onchange_finished_lot_id(self):
|
||||
res = self.production_id._can_produce_serial_number(sn=self.finished_lot_id)
|
||||
if res is not True:
|
||||
return res
|
||||
|
||||
def write(self, values):
|
||||
if 'production_id' in values:
|
||||
raise UserError(_('You cannot link this work order to another manufacturing order.'))
|
||||
@@ -847,16 +853,8 @@ class MrpWorkorder(models.Model):
|
||||
)
|
||||
|
||||
def _check_sn_uniqueness(self):
|
||||
""" Alert the user if the serial number as already been produced """
|
||||
if self.product_tracking == 'serial' and self.finished_lot_id:
|
||||
sml = self.env['stock.move.line'].search_count([
|
||||
('lot_id', '=', self.finished_lot_id.id),
|
||||
('location_id.usage', '=', 'production'),
|
||||
('qty_done', '=', 1),
|
||||
('state', '=', 'done')
|
||||
])
|
||||
if sml:
|
||||
raise UserError(_('This serial number for product %s has already been produced', self.product_id.name))
|
||||
# todo master: remove
|
||||
pass
|
||||
|
||||
def _update_qty_producing(self, quantity):
|
||||
self.ensure_one()
|
||||
|
||||
@@ -497,3 +497,90 @@ class TestTraceability(TestMrpCommon):
|
||||
|
||||
mo.button_mark_done()
|
||||
self.assertEqual(mo.state, 'done')
|
||||
|
||||
def test_unbuild_scrap_and_unscrap_tracked_component(self):
|
||||
"""
|
||||
Suppose a tracked-by-SN component C. There is one C in stock with SN01.
|
||||
Build a product P that uses C with SN, unbuild P, scrap SN, unscrap SN
|
||||
and rebuild a product with SN in the components
|
||||
"""
|
||||
warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
|
||||
stock_location = warehouse.lot_stock_id
|
||||
|
||||
component = self.bom_4.bom_line_ids.product_id
|
||||
component.write({
|
||||
'type': 'product',
|
||||
'tracking': 'serial',
|
||||
})
|
||||
serial_number = self.env['stock.production.lot'].create({
|
||||
'product_id': component.id,
|
||||
'name': 'Super Serial',
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
self.env['stock.quant']._update_available_quantity(component, stock_location, 1, lot_id=serial_number)
|
||||
|
||||
# produce 1
|
||||
mo_form = Form(self.env['mrp.production'])
|
||||
mo_form.bom_id = self.bom_4
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
mo.action_assign()
|
||||
self.assertEqual(mo.move_raw_ids.move_line_ids.lot_id, serial_number)
|
||||
|
||||
with Form(mo) as mo_form:
|
||||
mo_form.qty_producing = 1
|
||||
mo.move_raw_ids.move_line_ids.qty_done = 1
|
||||
mo.button_mark_done()
|
||||
|
||||
# unbuild
|
||||
action = mo.button_unbuild()
|
||||
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
|
||||
wizard.action_validate()
|
||||
|
||||
# scrap the component
|
||||
scrap = self.env['stock.scrap'].create({
|
||||
'product_id': component.id,
|
||||
'product_uom_id': component.uom_id.id,
|
||||
'scrap_qty': 1,
|
||||
'lot_id': serial_number.id,
|
||||
})
|
||||
scrap_location = scrap.scrap_location_id
|
||||
scrap.do_scrap()
|
||||
|
||||
# unscrap the component
|
||||
internal_move = self.env['stock.move'].create({
|
||||
'name': component.name,
|
||||
'location_id': scrap_location.id,
|
||||
'location_dest_id': stock_location.id,
|
||||
'product_id': component.id,
|
||||
'product_uom': component.uom_id.id,
|
||||
'product_uom_qty': 1.0,
|
||||
'move_line_ids': [(0, 0, {
|
||||
'product_id': component.id,
|
||||
'location_id': scrap_location.id,
|
||||
'location_dest_id': stock_location.id,
|
||||
'product_uom_id': component.uom_id.id,
|
||||
'qty_done': 1.0,
|
||||
'lot_id': serial_number.id,
|
||||
})],
|
||||
})
|
||||
internal_move._action_confirm()
|
||||
internal_move._action_done()
|
||||
|
||||
# produce one with the unscrapped component
|
||||
mo_form = Form(self.env['mrp.production'])
|
||||
mo_form.bom_id = self.bom_4
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
mo.action_assign()
|
||||
self.assertEqual(mo.move_raw_ids.move_line_ids.lot_id, serial_number)
|
||||
|
||||
with Form(mo) as mo_form:
|
||||
mo_form.qty_producing = 1
|
||||
mo.move_raw_ids.move_line_ids.qty_done = 1
|
||||
mo.button_mark_done()
|
||||
|
||||
self.assertRecordValues((mo.move_finished_ids + mo.move_raw_ids).move_line_ids, [
|
||||
{'product_id': self.bom_4.product_id.id, 'lot_id': False, 'qty_done': 1},
|
||||
{'product_id': component.id, 'lot_id': serial_number.id, 'qty_done': 1},
|
||||
])
|
||||
@@ -50,15 +50,16 @@ class Rating(http.Controller):
|
||||
'rate_names': rate_names, 'rate': rate
|
||||
})
|
||||
|
||||
@http.route(['/rate/<string:token>/submit_feedback'], type="http", auth="public", methods=['post'], website=True)
|
||||
@http.route(['/rate/<string:token>/submit_feedback'], type="http", auth="public", methods=['post', 'get'], website=True)
|
||||
def action_submit_rating(self, token, **kwargs):
|
||||
rate = int(kwargs.get('rate'))
|
||||
assert rate in (1, 3, 5), "Incorrect rating"
|
||||
rating = request.env['rating.rating'].sudo().search([('access_token', '=', token)])
|
||||
if not rating:
|
||||
return request.not_found()
|
||||
record_sudo = request.env[rating.res_model].sudo().browse(rating.res_id)
|
||||
record_sudo.rating_apply(rate, token=token, feedback=kwargs.get('feedback'))
|
||||
if request.httprequest.method == "POST":
|
||||
rate = int(kwargs.get('rate'))
|
||||
assert rate in (1, 3, 5), "Incorrect rating"
|
||||
record_sudo = request.env[rating.res_model].sudo().browse(rating.res_id)
|
||||
record_sudo.rating_apply(rate, token=token, feedback=kwargs.get('feedback'))
|
||||
lang = rating.partner_id.lang or get_lang(request.env).code
|
||||
return request.env['ir.ui.view'].with_context(lang=lang)._render_template('rating.rating_external_page_view', {
|
||||
'web_base_url': request.env['ir.config_parameter'].sudo().get_param('web.base.url'),
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_security
|
||||
from . import test_controller
|
||||
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
from flectra.tests import HttpCase, tagged, new_test_user
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestControllersRoute(HttpCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestControllersRoute, self).setUp()
|
||||
self.user = new_test_user(self.env, "test_user_1", email="test_user_1@nowhere.com", tz="UTC")
|
||||
self.partner = self.user.partner_id
|
||||
|
||||
def test_controller_rating(self):
|
||||
|
||||
rating_test = self.env['rating.rating'].with_user(self.user).create({
|
||||
'res_model_id': self.env['ir.model'].sudo().search([('model', '=', 'res.partner')], limit=1).id,
|
||||
'res_model': 'res.partner',
|
||||
'res_id': self.partner.id,
|
||||
'rating': 3
|
||||
})
|
||||
self.authenticate(None, None)
|
||||
access_token = rating_test.access_token
|
||||
url = '/rate/%s/submit_feedback' % access_token
|
||||
req = self.url_open(url)
|
||||
self.assertEqual(req.status_code, 200, "Response should = OK")
|
||||
Reference in new issue
Block a user