mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2026-08-17 16:54:42 -05:00
[PATCH] Upstream patch - 06052023
This commit is contained in:
@@ -989,6 +989,7 @@ class AccountMove(models.Model):
|
||||
domain = [
|
||||
('company_id', '=', self.company_id.id),
|
||||
('internal_type', '=', 'receivable' if self.move_type in ('out_invoice', 'out_refund', 'out_receipt') else 'payable'),
|
||||
('deprecated', '=', False),
|
||||
]
|
||||
return self.env['account.account'].search(domain, limit=1)
|
||||
|
||||
|
||||
@@ -472,9 +472,11 @@ class AccountPayment(models.Model):
|
||||
self.reconciled_statements_count = 0
|
||||
return
|
||||
|
||||
self.env['account.move'].flush()
|
||||
self.env['account.move.line'].flush()
|
||||
self.env['account.partial.reconcile'].flush()
|
||||
self.env['account.journal'].flush(fnames=['payment_debit_account_id', 'payment_credit_account_id'])
|
||||
self.env['account.payment'].flush(fnames=['move_id'])
|
||||
self.env['account.move'].flush(fnames=['move_type', 'payment_id', 'statement_line_id', 'journal_id'])
|
||||
self.env['account.move.line'].flush(fnames=['move_id', 'account_id', 'statement_line_id'])
|
||||
self.env['account.partial.reconcile'].flush(fnames=['debit_move_id', 'credit_move_id'])
|
||||
|
||||
self._cr.execute('''
|
||||
SELECT
|
||||
|
||||
@@ -12,3 +12,9 @@ class AccountMove(models.Model):
|
||||
if l.expense_id:
|
||||
l.expense_id.refuse_expense(reason=_("Payment Cancelled"))
|
||||
return super().button_cancel()
|
||||
|
||||
def button_draft(self):
|
||||
for line in self.line_ids:
|
||||
if line.expense_id:
|
||||
line.expense_id.sheet_id.write({'state': 'post'})
|
||||
return super().button_draft()
|
||||
|
||||
@@ -278,6 +278,8 @@ class HrExpense(models.Model):
|
||||
return super(HrExpense, self).unlink()
|
||||
|
||||
def write(self, vals):
|
||||
if 'sheet_id' in vals:
|
||||
self.env['hr.expense.sheet'].browse(vals['sheet_id']).check_access_rule('write')
|
||||
if 'tax_ids' in vals or 'analytic_account_id' in vals or 'account_id' in vals:
|
||||
if any(not expense.is_editable for expense in self):
|
||||
raise UserError(_('You are not authorized to edit this expense report.'))
|
||||
|
||||
@@ -350,3 +350,63 @@ class TestExpenses(TestExpenseCommon):
|
||||
'amount_paid': formatLang(self.env, 11.0, currency_obj=self.env.company.currency_id),
|
||||
'currency': self.env.company.currency_id
|
||||
})
|
||||
|
||||
def test_reset_move_to_draft(self):
|
||||
"""
|
||||
Test the state of an expense and its report
|
||||
after resetting the paid move to draft
|
||||
"""
|
||||
# Create expense and report
|
||||
expense = self.env['hr.expense'].create({
|
||||
'name': 'expense_1',
|
||||
'employee_id': self.expense_employee.id,
|
||||
'product_id': self.product_a.id,
|
||||
'unit_amount': 1000.00,
|
||||
})
|
||||
expense.action_submit_expenses()
|
||||
expense_sheet = expense.sheet_id
|
||||
|
||||
self.assertEqual(expense.state, 'draft', 'Expense state must be draft before sheet submission')
|
||||
self.assertEqual(expense_sheet.state, 'draft', 'Sheet state must be draft before submission')
|
||||
|
||||
# Submit report
|
||||
expense_sheet.action_submit_sheet()
|
||||
|
||||
self.assertEqual(expense.state, 'reported', 'Expense state must be reported after sheet submission')
|
||||
self.assertEqual(expense_sheet.state, 'submit', 'Sheet state must be submit after submission')
|
||||
|
||||
# Approve report
|
||||
expense_sheet.approve_expense_sheets()
|
||||
|
||||
self.assertEqual(expense.state, 'approved', 'Expense state must be draft after sheet approval')
|
||||
self.assertEqual(expense_sheet.state, 'approve', 'Sheet state must be draft after approval')
|
||||
|
||||
# Create move
|
||||
expense_sheet.action_sheet_move_create()
|
||||
|
||||
self.assertEqual(expense.state, 'approved', 'Expense state must be draft after posting move')
|
||||
self.assertEqual(expense_sheet.state, 'post', 'Sheet state must be draft after posting move')
|
||||
|
||||
# Pay move
|
||||
move = expense_sheet.account_move_id
|
||||
self.env['account.payment.register'].with_context(active_model='account.move', active_ids=move.ids).create({
|
||||
'amount': 1000.0,
|
||||
})._create_payments()
|
||||
|
||||
self.assertEqual(expense.state, 'done', 'Expense state must be done after payment')
|
||||
self.assertEqual(expense_sheet.state, 'done', 'Sheet state must be done after payment')
|
||||
|
||||
# Reset move to draft
|
||||
move.button_draft()
|
||||
|
||||
self.assertEqual(expense.state, 'approved', 'Expense state must be approved after resetting move to draft')
|
||||
self.assertEqual(expense_sheet.state, 'post', 'Sheet state must be done after resetting move to draft')
|
||||
|
||||
# Post and pay move again
|
||||
move.action_post()
|
||||
self.env['account.payment.register'].with_context(active_model='account.move', active_ids=move.ids).create({
|
||||
'amount': 1000.0,
|
||||
})._create_payments()
|
||||
|
||||
self.assertEqual(expense.state, 'done', 'Expense state must be done after payment')
|
||||
self.assertEqual(expense_sheet.state, 'done', 'Sheet state must be done after payment')
|
||||
|
||||
@@ -10,12 +10,14 @@ import psycopg2
|
||||
|
||||
from flectra import api, exceptions, fields, models, _, SUPERUSER_ID
|
||||
from flectra.tools import consteq, float_round, image_process, ustr
|
||||
from flectra.exceptions import ValidationError
|
||||
from flectra.exceptions import UserError, ValidationError
|
||||
from flectra.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from flectra.tools.misc import formatLang
|
||||
from flectra.http import request
|
||||
from flectra.osv import expression
|
||||
|
||||
from flectra.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -336,6 +338,19 @@ class PaymentAcquirer(models.Model):
|
||||
self._check_required_if_provider()
|
||||
return result
|
||||
|
||||
def unlink(self):
|
||||
""" Prevent the deletion of the payment acquirer if it has an xmlid. """
|
||||
external_ids = self.get_external_id()
|
||||
for acquirer in self:
|
||||
external_id = external_ids[acquirer.id]
|
||||
if external_id \
|
||||
and not external_id.startswith('__export__') \
|
||||
and not self._context.get(MODULE_UNINSTALL_FLAG):
|
||||
raise UserError(
|
||||
_("You cannot delete the payment acquirer %s; archive it instead.", acquirer.name)
|
||||
)
|
||||
return super().unlink()
|
||||
|
||||
def get_acquirer_extra_fees(self, amount, currency_id, country_id):
|
||||
extra_fees = {
|
||||
'currency_id': currency_id
|
||||
|
||||
@@ -18,7 +18,6 @@ class ReportStockQuantity(models.Model):
|
||||
('out', 'Forecasted Deliveries'),
|
||||
], string='State', readonly=True)
|
||||
product_qty = fields.Float(string='Quantity', readonly=True)
|
||||
move_ids = fields.One2many('stock.move', readonly=True)
|
||||
company_id = fields.Many2one('res.company', readonly=True)
|
||||
warehouse_id = fields.Many2one('stock.warehouse', readonly=True)
|
||||
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// Fix dropzones of nested sortable.
|
||||
.mjs-nestedSortable-error {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.o_modal_header {
|
||||
@include o-webclient-padding($top: 10px, $bottom: 10px);
|
||||
@include clearfix;
|
||||
|
||||
@@ -236,12 +236,26 @@ class Website(Home):
|
||||
|
||||
return request.make_response(content, [('Content-Type', mimetype)])
|
||||
|
||||
@http.route('/website/info', type='http', auth="public", website=True, sitemap=True)
|
||||
def sitemap_website_info(env, rule, qs):
|
||||
website = env['website'].get_current_website()
|
||||
if not (
|
||||
website.viewref('website.website_info', False).active
|
||||
and website.viewref('website.show_website_info', False).active
|
||||
):
|
||||
# avoid 404 or blank page in sitemap
|
||||
return False
|
||||
|
||||
if not qs or qs.lower() in '/website/info':
|
||||
yield {'loc': '/website/info'}
|
||||
|
||||
@http.route('/website/info', type='http', auth="public", website=True, sitemap=sitemap_website_info)
|
||||
def website_info(self, **kwargs):
|
||||
try:
|
||||
request.website.get_template('website.website_info').name
|
||||
except Exception as e:
|
||||
return request.env['ir.http']._handle_exception(e)
|
||||
if not request.website.viewref('website.website_info', False).active:
|
||||
# Deleted or archived view (through manual operation in backend).
|
||||
# Don't check `show_website_info` view: still need to access if
|
||||
# disabled to be able to enable it through the customize show.
|
||||
raise request.not_found()
|
||||
|
||||
Module = request.env['ir.module.module'].sudo()
|
||||
apps = Module.search([('state', '=', 'installed'), ('application', '=', True)])
|
||||
l10n = Module.search([('state', '=', 'installed'), ('name', '=like', 'l10n_%')])
|
||||
|
||||
@@ -8,6 +8,7 @@ import mimetypes
|
||||
from werkzeug.utils import redirect
|
||||
|
||||
from flectra import http
|
||||
from flectra.exceptions import AccessError
|
||||
from flectra.http import request
|
||||
from flectra.addons.sale.controllers.portal import CustomerPortal
|
||||
from flectra.addons.website_sale.controllers.main import WebsiteSale
|
||||
@@ -81,25 +82,25 @@ class WebsiteSaleDigital(CustomerPortal):
|
||||
else:
|
||||
return redirect(self.orders_page)
|
||||
|
||||
# Check if the user has bought the associated product
|
||||
res_model = attachment['res_model']
|
||||
res_id = attachment['res_id']
|
||||
purchased_products = request.env['account.move.line'].get_digital_purchases()
|
||||
try:
|
||||
request.env['ir.attachment'].browse(attachment_id).check('read')
|
||||
except AccessError: # The user does not have read access on the attachment.
|
||||
# Check if access can be granted through their purchases.
|
||||
res_model = attachment['res_model']
|
||||
res_id = attachment['res_id']
|
||||
digital_purchases = request.env['account.move.line'].get_digital_purchases()
|
||||
if res_model == 'product.product':
|
||||
purchased_product_ids = digital_purchases
|
||||
elif res_model == 'product.template':
|
||||
purchased_product_ids = request.env['product.product'].sudo().browse(
|
||||
digital_purchases
|
||||
).mapped('product_tmpl_id').ids
|
||||
else:
|
||||
purchased_product_ids = [] # The purchases must be related to products.
|
||||
if res_id not in purchased_product_ids: # No related purchase was found.
|
||||
return redirect(self.orders_page) # Prevent the user from downloading.
|
||||
|
||||
if res_model == 'product.product':
|
||||
if res_id not in purchased_products:
|
||||
return redirect(self.orders_page)
|
||||
|
||||
# Also check for attachments in the product templates
|
||||
elif res_model == 'product.template':
|
||||
template_ids = request.env['product.product'].sudo().browse(purchased_products).mapped('product_tmpl_id').ids
|
||||
if res_id not in template_ids:
|
||||
return redirect(self.orders_page)
|
||||
|
||||
else:
|
||||
return redirect(self.orders_page)
|
||||
|
||||
# The client has bought the product, otherwise it would have been blocked by now
|
||||
# The user has bought the product, or has the rights to the attachment
|
||||
if attachment["type"] == "url":
|
||||
if attachment["url"]:
|
||||
return redirect(attachment["url"])
|
||||
|
||||
@@ -46,10 +46,11 @@ class SlidesPortalChatter(PortalChatter):
|
||||
# fetch and update mail.message
|
||||
message_id = int(message_id)
|
||||
message_body = plaintext2html(message)
|
||||
subtype_comment_id = request.env['ir.model.data'].xmlid_to_res_id('mail.mt_comment')
|
||||
domain = [
|
||||
('model', '=', res_model),
|
||||
('res_id', '=', res_id),
|
||||
('is_internal', '=', False),
|
||||
('subtype_id', '=', subtype_comment_id),
|
||||
('author_id', '=', request.env.user.partner_id.id),
|
||||
('message_type', '=', 'comment'),
|
||||
('id', '=', message_id)
|
||||
@@ -64,7 +65,7 @@ class SlidesPortalChatter(PortalChatter):
|
||||
|
||||
# update rating
|
||||
if post.get('rating_value'):
|
||||
domain = [('res_model', '=', res_model), ('res_id', '=', res_id), ('is_internal', '=', False), ('message_id', '=', message.id)]
|
||||
domain = [('res_model', '=', res_model), ('res_id', '=', res_id), ('message_id', '=', message.id)]
|
||||
rating = request.env['rating.rating'].sudo().search(domain, order='write_date DESC', limit=1)
|
||||
rating.write({
|
||||
'rating': float(post['rating_value']),
|
||||
|
||||
@@ -484,12 +484,13 @@ class WebsiteSlides(WebsiteProfile):
|
||||
'enable_slide_upload': 'enable_slide_upload' in kw,
|
||||
}
|
||||
if not request.env.user._is_public():
|
||||
subtype_comment_id = request.env['ir.model.data'].xmlid_to_res_id('mail.mt_comment')
|
||||
last_message = request.env['mail.message'].search([
|
||||
('model', '=', channel._name),
|
||||
('res_id', '=', channel.id),
|
||||
('author_id', '=', request.env.user.partner_id.id),
|
||||
('message_type', '=', 'comment'),
|
||||
('is_internal', '=', False)
|
||||
('subtype_id', '=', subtype_comment_id)
|
||||
], order='write_date DESC', limit=1)
|
||||
if last_message:
|
||||
last_message_values = last_message.read(['body', 'rating_value', 'attachment_ids'])[0]
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
flectra.define('website_slides.tour.slide.course.reviews', function (require) {
|
||||
'use strict';
|
||||
|
||||
const tour = require('web_tour.tour');
|
||||
|
||||
/**
|
||||
* This tour test that a log note isn't considered
|
||||
* as a course review. And also that a member can
|
||||
* add only one review.
|
||||
*/
|
||||
tour.register('course_reviews', {
|
||||
url: '/slides',
|
||||
test: true
|
||||
}, [
|
||||
{
|
||||
trigger: 'a:contains("Basics of Gardening - Test")',
|
||||
}, {
|
||||
trigger: 'a[id="review-tab"]',
|
||||
}, {
|
||||
trigger: '.o_portal_chatter_message:contains("Log note")',
|
||||
run: function() {},
|
||||
}, {
|
||||
trigger: 'button:contains("Add a review")',
|
||||
// If it fails here, it means the log note is considered as a review
|
||||
}, {
|
||||
trigger: 'form.o_portal_chatter_composer_form textarea',
|
||||
extra_trigger: 'div#ratingpopupcomposer.modal_shown',
|
||||
run: 'text Great course!',
|
||||
in_modal: false,
|
||||
}, {
|
||||
trigger: 'button.o_portal_chatter_composer_btn',
|
||||
in_modal: false,
|
||||
}, {
|
||||
trigger: 'a[id="review-tab"]',
|
||||
}, {
|
||||
trigger: 'button:contains("Visible")',
|
||||
}, {
|
||||
trigger: 'button:contains("Modify your review")',
|
||||
// If it fails here, it means the system is allowing you to add another review.
|
||||
}, {
|
||||
trigger: 'form.o_portal_chatter_composer_form textarea:contains("Great course!")',
|
||||
run: function() {},
|
||||
}
|
||||
]);
|
||||
|
||||
});
|
||||
@@ -19,7 +19,7 @@ class TestUICommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal):
|
||||
img_path = get_module_resource('website_slides', 'static', 'src', 'img', 'slide_demo_gardening_1.jpg')
|
||||
img_content = base64.b64encode(open(img_path, "rb").read())
|
||||
|
||||
self.env['slide.channel'].create({
|
||||
self.channel = self.env['slide.channel'].create({
|
||||
'name': 'Basics of Gardening - Test',
|
||||
'user_id': self.env.ref('base.user_admin').id,
|
||||
'enroll': 'public',
|
||||
@@ -148,6 +148,24 @@ class TestUi(TestUICommon):
|
||||
'flectra.__DEBUG__.services["web_tour.tour"].tours.full_screen_web_editor.ready',
|
||||
login=user_demo.login)
|
||||
|
||||
def test_course_reviews_elearning_officer(self):
|
||||
user_demo = self.user_demo
|
||||
user_demo.write({
|
||||
'groups_id': [(6, 0, (self.env.ref('base.group_user') | self.env.ref(
|
||||
'website_slides.group_website_slides_officer')).ids)]
|
||||
})
|
||||
|
||||
# The user must be a course member before being able to post a log note.
|
||||
self.channel._action_add_members(user_demo.partner_id)
|
||||
self.channel.with_user(user_demo).message_post(
|
||||
body='Log note', subtype_xmlid='mail.mt_note', message_type='comment')
|
||||
|
||||
self.browser_js(
|
||||
'/slides',
|
||||
'flectra.__DEBUG__.services["web_tour.tour"].run("course_reviews")',
|
||||
'flectra.__DEBUG__.services["web_tour.tour"].tours.course_reviews.ready',
|
||||
login=user_demo.login)
|
||||
|
||||
|
||||
@tests.common.tagged('external', 'post_install', '-standard', '-at_install')
|
||||
class TestUiYoutube(HttpCaseWithUserDemo):
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
<script type="text/javascript" src="/website_slides/static/src/tests/tours/slides_course_member.js"/>
|
||||
<script type="text/javascript" src="/website_slides/static/src/tests/tours/slides_course_member_yt.js"/>
|
||||
<script type="text/javascript" src="/website_slides/static/src/tests/tours/slides_course_publisher.js"/>
|
||||
<script type="text/javascript" src="/website_slides/static/src/tests/tours/slides_course_reviews.js"/>
|
||||
<script type="text/javascript" src="/website_slides/static/src/tests/tours/slides_full_screen_web_editor.js"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user