diff --git a/odex25_purchase/odex25_annual_purchase/__manifest__.py b/odex25_purchase/odex25_annual_purchase/__manifest__.py index d35494e7e..19c93fc63 100644 --- a/odex25_purchase/odex25_annual_purchase/__manifest__.py +++ b/odex25_purchase/odex25_annual_purchase/__manifest__.py @@ -12,11 +12,14 @@ "data/sequence.xml", "views/annual_request_views.xml", - "views/purchase_inherit_views.xml", "views/addendum_views.xml", "views/purchase_requisition.xml", "data/mail_activity.xml", "views/menu.xml", + "views/annual_rfq_views.xml", + "data/mail_template_annual_rfq.xml", + "views/select_reason_rfq_views.xml", + "views/report_annual_rfq.xml", "wizard/annual_cancel_wizard_view.xml" ], "application": True, diff --git a/odex25_purchase/odex25_annual_purchase/data/mail_template_annual_rfq.xml b/odex25_purchase/odex25_annual_purchase/data/mail_template_annual_rfq.xml new file mode 100644 index 000000000..65db176fd --- /dev/null +++ b/odex25_purchase/odex25_annual_purchase/data/mail_template_annual_rfq.xml @@ -0,0 +1,33 @@ + + + + + Purchase Order: Send RFQ + + RFQ ${object.name or ''} + +
+

+ Dear ${object.vendor_id.name} + % if object.vendor_id.parent_id: + (${object.vendor_id.parent_id.name}) + % endif +

+ Here is in attachment a request for quotation ${object.name} + % if object.partner_ref: + with reference: ${object.partner_ref} + % endif + from ${object.company_id.name}. +

+ If you have any questions, please do not hesitate to contact us. +

+ Best regards, +

+
+ + ${(object.name or '').replace('/','_')} + ${object.vendor_id.lang} + +
+
+
\ No newline at end of file diff --git a/odex25_purchase/odex25_annual_purchase/data/sequence.xml b/odex25_purchase/odex25_annual_purchase/data/sequence.xml index 692fbcf8f..24dfa2afa 100644 --- a/odex25_purchase/odex25_annual_purchase/data/sequence.xml +++ b/odex25_purchase/odex25_annual_purchase/data/sequence.xml @@ -11,4 +11,13 @@ AD%(y)s- 4 + + Annual RFQ Sequence + annual.rfq + RFQ + 5 + 1 + 1 + + diff --git a/odex25_purchase/odex25_annual_purchase/i18n/ar_001.po b/odex25_purchase/odex25_annual_purchase/i18n/ar_001.po index 9114e42f8..848708f44 100644 --- a/odex25_purchase/odex25_annual_purchase/i18n/ar_001.po +++ b/odex25_purchase/odex25_annual_purchase/i18n/ar_001.po @@ -589,6 +589,7 @@ msgid "Please set Selected Vendor." msgstr "يرجى تحديد المورد المختار." #. module: odex25_annual_purchase +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq_line__product_id #: model:ir.model.fields,field_description:odex25_annual_purchase.field_odx_annual_addendum_line__product_id #: model:ir.model.fields,field_description:odex25_annual_purchase.field_odx_annual_request_line__product_id msgid "Product" @@ -638,6 +639,7 @@ msgid "Purpose" msgstr "مبررات طلب الشراء (الغرض)" #. module: odex25_annual_purchase +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq_line__quantity #: model:ir.model.fields,field_description:odex25_annual_purchase.field_odx_annual_addendum_line__quantity #: model:ir.model.fields,field_description:odex25_annual_purchase.field_odx_annual_request_line__quantity msgid "Quantity" @@ -688,11 +690,7 @@ msgstr "موصى به من قبل اللجنة" msgid "Reference" msgstr "المرجع" -#. module: odex25_annual_purchase -#: code:addons/odex25_annual_purchase/models/purchase_inherit.py:0 -#, python-format -msgid "Refuse Reason" -msgstr "سبب الرفض" + #. module: odex25_annual_purchase #: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_odx_annual_request_form @@ -774,12 +772,6 @@ msgstr "موافقة مدير الخدمات المشتركة؟" msgid "SSD Reject" msgstr "رفض مدير الخدمات المشتركة" -#. module: odex25_annual_purchase -#: code:addons/odex25_annual_purchase/models/purchase_inherit.py:0 -#, python-format -msgid "Select Reason" -msgstr "اختر السبب" - #. module: odex25_annual_purchase #: model:ir.model.fields,field_description:odex25_annual_purchase.field_odx_annual_request__vendor_id msgid "Selected Vendor" @@ -813,19 +805,6 @@ msgstr "مدير الخدمات المشتركة" msgid "Sorry, No Committee members" msgstr "عفواً، لا يوجد أعضاء في اللجنة" -#. module: odex25_annual_purchase -#: code:addons/odex25_annual_purchase/models/purchase_inherit.py:0 -#, python-format -msgid "Sorry, the minimum number of committee votes is not satisfied" -msgstr "عفواً، لم يتم استيفاء الحد الأدنى من أصوات اللجنة" - -#. module: odex25_annual_purchase -#: code:addons/odex25_annual_purchase/models/purchase_inherit.py:0 -#, python-format -msgid "" -"Sorry, you cannot sign this quotation. You need more committee members to " -"choose it" -msgstr "عفواً، لا يمكنك توقيع عرض السعر هذا. أنت بحاجة إلى المزيد من أعضاء اللجنة لاختياره" #. module: odex25_annual_purchase #: model:ir.model.fields,field_description:odex25_annual_purchase.field_odx_annual_request__date_start @@ -875,6 +854,7 @@ msgid "Type of the exception activity on record." msgstr "نوع النشاط الاستثنائي في السجل." #. module: odex25_annual_purchase +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq_line__price_unit #: model:ir.model.fields,field_description:odex25_annual_purchase.field_odx_annual_addendum_line__price_unit #: model:ir.model.fields,field_description:odex25_annual_purchase.field_odx_annual_request_line__price_unit msgid "Unit Price" @@ -892,17 +872,15 @@ msgstr "رسائل غير مقروءة" msgid "Unread Messages Counter" msgstr "عداد الرسائل غير المقروءة" + #. module: odex25_annual_purchase +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq_line__uom_id #: model:ir.model.fields,field_description:odex25_annual_purchase.field_odx_annual_addendum_line__uom_id #: model:ir.model.fields,field_description:odex25_annual_purchase.field_odx_annual_request_line__uom_id -#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_purchase_order_form_annual_rfq_odx msgid "UoM" msgstr "وحدة القياس" -#. module: odex25_annual_purchase -#: model:ir.model.fields,field_description:odex25_annual_purchase.field_odx_annual_addendum__vendor_id -msgid "Vendor" -msgstr "المورد" + #. module: odex25_annual_purchase #: model:ir.model.fields.selection,name:odex25_annual_purchase.selection__odx_annual_request__state__to_manager @@ -948,15 +926,6 @@ msgstr "" msgid "اسم القسم" msgstr "" -#. module: odex25_annual_purchase -#: code:addons/odex25_annual_purchase/models/purchase_inherit.py:0 -#: code:addons/odex25_annual_purchase/models/purchase_inherit.py:0 -#: code:addons/odoo/STANDARD_MODULES/test/odex25_purchase/odex25_purchase/odex25_annual_purchase/models/purchase_inherit.py:0 -#: code:addons/odoo/STANDARD_MODULES/test/odex25_purchase/odex25_purchase/odex25_annual_purchase/models/purchase_inherit.py:0 -#, python-format -msgid "تم رفض عرض السعر من قبل جميع أعضاء اللجنة." -msgstr "" - #. module: odex25_annual_purchase #: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_purchase_order_form_annual_rfq_odx msgid "عناصر الطلب" @@ -1006,10 +975,13 @@ msgstr "الملاحظة" #. module: odex25_annual_purchase +#: code:addons/odex25_annual_purchase/models/annual_request.py:0 +#: code:addons/odoo/STANDARD_MODULES/test/odex25_purchase/odex25_purchase/odex25_annual_purchase/models/annual_request.py:0 +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq__attach_no #: model:ir.model.fields,field_description:odex25_annual_purchase.field_odx_annual_request__attach_no +#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_annual_rfq_form #: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_odx_annual_request_form -#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_po_form_products_for_annual_committee -#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_po_form_products_for_tech_committee +#, python-format msgid "Documents" msgstr "المرفقات" @@ -1040,9 +1012,8 @@ msgstr "رفض" #. module: odex25_annual_purchase +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq_line__description #: model:ir.model.fields,field_description:odex25_annual_purchase.field_odx_annual_request_line__description -#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_po_form_products_for_annual_committee -#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_po_form_products_for_tech_committee msgid "Technical Description" msgstr "المواصفات الفنية" @@ -1057,8 +1028,199 @@ msgstr "هل أنت متأكد أنك تريد المتابعة؟" + + + + + + + + + + + + + + + +#. module: odex25_annual_purchase +#: code:addons/odex25_annual_purchase/models/annual_rfq.py:0 +#: model:ir.model,name:odex25_annual_purchase.model_annual_rfq +#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_annual_rfq_form +#, python-format +msgid "Request for Quotation" +msgstr "طلب عرض سعر" + + +#. module: odex25_annual_purchase +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq__recommendation_order +msgid "Recommend" +msgstr "موصى به" + +#. module: odex25_annual_purchase +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq__partner_id +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq__vendor_id +msgid "Vendor" +msgstr "المورد" + +#. module: odex25_annual_purchase +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq__partner_ref +msgid "Partner Ref" +msgstr "رقم إشارة المورد" + + +#. module: odex25_annual_purchase +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq__source_request_ref +msgid "Source Request Reference" +msgstr "المستند المصدر" + + +#. module: odex25_annual_purchase +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq__no_of_approve +msgid "No. of Votes " +msgstr "عدد الاختيارات" + + +#. module: odex25_annual_purchase +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq__user_id +msgid "Purchase Representative" +msgstr "مندوب الشراء" + +#. module: odex25_annual_purchase +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq_line__subtotal +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_odx_annual_addendum_line__price_subtotal +#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_annual_rfq_form +msgid "Subtotal" +msgstr "الإجمالي الفرعي" + +#. module: odex25_annual_purchase +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq__amount_tax +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq_line__taxes_id +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_odx_annual_addendum_line__taxes_id +msgid "Taxes" +msgstr "الضرائب" + #. module: odex25_annual_purchase #: model:ir.model.fields.selection,name:odex25_annual_purchase.selection__odx_annual_request__committee_status__recommended -#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_po_form_products_for_annual_committee +#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_annual_rfq_form msgid "Recommended" msgstr "موصى به" + +#. module: odex25_annual_purchase +#: model:ir.model.fields.selection,name:odex25_annual_purchase.selection__annual_rfq__state__draft +#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_annual_rfq_form +msgid "Sign" +msgstr "تنفيذ عرض سعر" + + +#. module: odex25_annual_purchase +#: model:ir.model.fields.selection,name:odex25_annual_purchase.selection__annual_rfq__state__sent +msgid "Sent to Vendor" +msgstr "تم إرسال طلب عرض سعر" + + +#. module: odex25_annual_purchase +#: model:ir.model.fields.selection,name:odex25_annual_purchase.selection__annual_rfq__state__committee +msgid "Committees" +msgstr "اللجنة" + +#. module: odex25_annual_purchase +#: model:ir.model.fields.selection,name:odex25_annual_purchase.selection__annual_rfq__state__po +msgid "Purchase Order" +msgstr "أمر شراء" + +#. module: odex25_annual_purchase +#: code:addons/odex25_annual_purchase/models/annual_rfq.py:0 +#: code:addons/odoo/STANDARD_MODULES/test/odex25_purchase/odex25_purchase/odex25_annual_purchase/models/annual_rfq.py:0 +#, python-format +msgid "" +"Sorry You cannot sign this quotation ,YOU NEED MORE COMMITTE MEMBERS TO " +"choose it" +msgstr "عذرًا، لا يمكنك اعتماد هذا العرض، تحتاج إلى عدد أكبر من أعضاء اللجنة لاختياره." + +#. module: odex25_annual_purchase +#: code:addons/odex25_annual_purchase/models/annual_rfq.py:0 +#: code:addons/odoo/STANDARD_MODULES/test/odex25_purchase/odex25_purchase/odex25_annual_purchase/models/annual_rfq.py:0 +#, python-format +msgid "Cannot continue: the source document is Approval stage." +msgstr "لا يمكن المتابعة: المستند المصدر في مرحلة الاعتماد." + +#. module: odex25_annual_purchase +#: code:addons/odex25_annual_purchase/models/annual_rfq.py:0 +#: code:addons/odoo/STANDARD_MODULES/test/odex25_purchase/odex25_purchase/odex25_annual_purchase/models/annual_rfq.py:0 +#, python-format +msgid "You have already refused this RFQ before." +msgstr "لقد قمت برفض هذا العرض مسبقًا." + +#. module: odex25_annual_purchase +#: code:addons/odex25_annual_purchase/models/annual_rfq.py:0 +#: code:addons/odoo/STANDARD_MODULES/test/odex25_purchase/odex25_purchase/odex25_annual_purchase/models/annual_rfq.py:0 +#, python-format +msgid "You have already selected this RFQ before." +msgstr "لقد قمت باختيار هذا العرض مسبقًا." + +#. module: odex25_annual_purchase +#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_select_reason_rfq_form +msgid "Provide Recommendation" +msgstr "تقديم التوصية" + +#. module: odex25_annual_purchase +#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_refuse_reason_rfq_form +msgid "Provide Refuse Reason" +msgstr "تقديم سبب الرفض" + +#. module: odex25_annual_purchase +#: code:addons/odex25_annual_purchase/models/annual_rfq.py:0 +#: code:addons/odoo/STANDARD_MODULES/test/odex25_purchase/odex25_purchase/odex25_annual_purchase/models/annual_rfq.py:0 +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_select_reason_rfq__select_reason +#, python-format +msgid "Select Reason" +msgstr "سبب الاختيار" + +#. module: odex25_annual_purchase +#: code:addons/odex25_annual_purchase/models/committe.py:0 +#: code:addons/odoo/STANDARD_MODULES/test/odex25_purchase/odex25_purchase/odex25_annual_purchase/models/committe.py:0 +#, python-format +msgid "Recommended by %s: %s" +msgstr "تمت التوصية من قبل %s: %s" + +#. module: odex25_annual_purchase +#: code:addons/odex25_annual_purchase/models/annual_rfq.py:0 +#: code:addons/odoo/STANDARD_MODULES/test/odex25_purchase/odex25_purchase/odex25_annual_purchase/models/annual_rfq.py:0 +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_refuse_reason_rfq__refuse_reason +#, python-format +msgid "Refuse Reason" +msgstr "سبب الرفض" + +#. module: odex25_annual_purchase +#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_annual_rfq_form +msgid "Send by Email" +msgstr "إرسال بالبريد الإلكتروني" + + +#. module: odex25_annual_purchase +#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_annual_rfq_form +msgid "Select" +msgstr "اختيار" + +#. module: odex25_annual_purchase +#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_annual_rfq_form +msgid "Refuse" +msgstr "رفض" + +#. module: odex25_annual_purchase +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq__amount_untaxed +msgid "Untaxed Amount" +msgstr "المبلغ قبل الضريبة" + +#. module: odex25_annual_purchase +#: model:ir.model.fields,field_description:odex25_annual_purchase.field_annual_rfq__amount_total +msgid "Total" +msgstr "الإجمالي" + + + +#. module: odex25_annual_purchase +#: model_terms:ir.ui.view,arch_db:odex25_annual_purchase.view_annual_rfq_form +msgid "Committe Members" +msgstr "أعضاء اللجنة" diff --git a/odex25_purchase/odex25_annual_purchase/models/__init__.py b/odex25_purchase/odex25_annual_purchase/models/__init__.py index 626671342..a51b05997 100644 --- a/odex25_purchase/odex25_annual_purchase/models/__init__.py +++ b/odex25_purchase/odex25_annual_purchase/models/__init__.py @@ -1,4 +1,5 @@ from . import annual_request -from . import purchase_inherit from . import addendum from . import purchase_requisition +from . import annual_rfq +from . import committe diff --git a/odex25_purchase/odex25_annual_purchase/models/annual_request.py b/odex25_purchase/odex25_annual_purchase/models/annual_request.py index cfc3f5d2a..66a15ea20 100644 --- a/odex25_purchase/odex25_annual_purchase/models/annual_request.py +++ b/odex25_purchase/odex25_annual_purchase/models/annual_request.py @@ -56,8 +56,6 @@ class AnnualPurchaseRequest(models.Model): ssd_approve = fields.Boolean(string="SSD Approve", default=False) seo_approve = fields.Boolean(string="SEO Approve", default=False) - rfq_count = fields.Integer(string='RFQs/POs', compute='_compute_counts') - po_count = fields.Integer(string='PO Count', compute='_compute_counts') can_create_agreement = fields.Boolean(compute='_compute_can_create_agreement') @@ -79,6 +77,64 @@ class AnnualPurchaseRequest(models.Model): readonly=True, ) addendum_count = fields.Integer(string='Addendums', compute='_compute_addendum_count', readonly=True) + + rfq_ids = fields.One2many('annual.rfq', 'source_request_ref', string="RFQs") + rfq_count = fields.Integer(compute='_compute_rfq_count', string='RFQs') + + def _compute_rfq_count(self): + for rec in self: + rec.rfq_count = len(rec.rfq_ids) + + def action_create_rfq(self): + """Create an RFQ (annual.rfq) from this annual request and copy its lines.""" + for rec in self: + + rfq_vals = { + 'source_request_ref': rec.id, + 'vendor_id': rec.vendor_id.id, + 'request_date': fields.Date.context_today(self), + 'purpose': rec.purpose or rec.description or '', + 'note': rec.note or '', + 'company_id': rec.env.company.id, + 'currency_id': rec.currency_id.id, + } + + line_commands = [] + for l in rec.line_ids: + line_commands.append((0, 0, { + 'product_id': l.product_id.id, + 'description': l.description or l.technical_spec or l.product_id.display_name, + 'quantity': l.quantity or 1.0, + 'uom_id': (l.uom_id and l.uom_id.id) or l.product_id.uom_po_id.id, + 'price_unit': l.price_unit or 0.0, + # taxes تُحدّد لاحقًا من المستخدم (إن رغبت أضف من تصنيف المنتج) + })) + rfq_vals['line_ids'] = line_commands + + rfq = self.env['annual.rfq'].create(rfq_vals) + + return { + 'name': _('RFQ'), + 'type': 'ir.actions.act_window', + 'res_model': 'annual.rfq', + 'view_mode': 'form', + 'res_id': rfq.id, + 'target': 'current', + } + def action_open_rfqs(self): + self.ensure_one() + return { + 'name': _('RFQs'), + 'type': 'ir.actions.act_window', + 'res_model': 'annual.rfq', + 'view_mode': 'tree,form', + 'domain': [('source_request_ref', '=', self.id)], + 'context': {'default_source_request_ref': self.id, + 'default_vendor_id': self.vendor_id.id or False}, + 'target': 'current', + } + + def _compute_addendum_count(self): Addendum = self.env['odx.annual.addendum'] for rec in self: @@ -116,25 +172,17 @@ class AnnualPurchaseRequest(models.Model): ('res_id', '=', rec.id) ]) - purchase_orders = self.env['purchase.order'].search([('annual_request_id', '=', rec.id)]) - count_po = 0 - if purchase_orders: - count_po = Attachment.search_count([ - ('res_model', '=', 'purchase.order'), - ('res_id', 'in', purchase_orders.ids) - ]) - rec.attach_no = count_self + count_po + + rec.attach_no = count_self def get_attachments(self): self.ensure_one() Attachment = self.env['ir.attachment'] - purchase_orders = self.env['purchase.order'].search([('annual_request_id', '=', self.id)]) domain = ['|', - '&', ('res_model', '=', self._name), ('res_id', 'in', self.ids), - '&', ('res_model', '=', 'purchase.order'), ('res_id', 'in', purchase_orders.ids) + '&', ('res_model', '=', self._name), ('res_id', 'in', self.ids) ] return { @@ -224,91 +272,9 @@ class AnnualPurchaseRequest(models.Model): for rec in self: rec.can_create_agreement = rec.state in ('ceo','approved') and not rec.agreement_id and bool(rec.vendor_id) - @api.depends('agreement_id') - def _compute_counts(self): - PO = self.env['purchase.order'] - for rec in self: - rec.rfq_count = PO.search_count([('annual_request_id','=',rec.id)]) - if rec.agreement_id: - rec.po_count = PO.search_count([('requisition_id','=',rec.agreement_id.id)]) - else: - rec.po_count = 0 - def action_open_rfqs(self): - self.ensure_one() - domain = [('annual_request_id', '=', self.id)] - rfqs = self.env['purchase.order'].search(domain) - if len(rfqs) == 1: - return { - 'name': _('RFQ'), - 'type': 'ir.actions.act_window', - 'res_model': 'purchase.order', - 'view_mode': 'form', - 'res_id': rfqs.id, - 'target': 'current', - 'context': {'default_annual_request_id': self.id, 'default_origin': self.name}, - } - - return { - 'name': _('RFQs / Purchase Orders'), - 'type': 'ir.actions.act_window', - 'res_model': 'purchase.order', - 'views': [(False, 'tree'), (False, 'form')], - 'domain': domain, - 'context': {'default_annual_request_id': self.id, 'default_origin': self.name}, - } - - def action_create_rfq(self): - self.ensure_one() - self._check_lines() - order_line_vals = [] - for line in self.line_ids: - line.sudo() - order_line_vals.append((0, 0, { - 'product_id': line.product_id.id, - 'name': line.description or line.product_id.description_purchase or line.product_id.name, - 'product_qty': line.quantity, - 'product_uom': line.uom_id.id, - 'price_unit': line.price_unit or 0.0, - 'date_planned': fields.Datetime.now(), - 'account_analytic_id': self.department_id and self.department_id.analytic_account_id and self.department_id.analytic_account_id.id or False, - 'taxes_id': [(6, 0, line.product_id.supplier_taxes_id.ids)], - })) - - # PO = self.env['purchase.order'].sudo().create({ - # 'origin': self.name, - # 'annual_request_id': self.id, - # 'department_id': self.department_id.id if hasattr(self.department_id, 'id') else False, - # 'purpose': self.purpose, - # 'is_recommended': False, - # 'allow_empty_vendor': True, - # 'order_line': order_line_vals, - # 'purchase_commitee': self.committee_enabled, - # }) - # - # if self.committee_enabled: - # PO._copy_committee_from_annual_request() - - return { - 'name': _("Annual RFQ"), - 'type': 'ir.actions.act_window', - 'res_model': 'purchase.order', - 'view_mode': 'form', - 'target': 'current', - 'context': { - 'default_origin': self.name, - 'default_annual_request_id': self.id, - 'default_department_id': self.department_id.id if self.department_id else False, - 'default_purpose': self.purpose, - 'default_is_recommended': False, - - 'default_partner_id': False, - - 'default_order_line': order_line_vals, - }, - } def action_open_agreement(self): self.ensure_one() diff --git a/odex25_purchase/odex25_annual_purchase/models/annual_rfq.py b/odex25_purchase/odex25_annual_purchase/models/annual_rfq.py new file mode 100644 index 000000000..cd38b0cbf --- /dev/null +++ b/odex25_purchase/odex25_annual_purchase/models/annual_rfq.py @@ -0,0 +1,359 @@ +# -*- coding: utf-8 -*- +from odoo import api, fields, models, _ +from odoo.exceptions import UserError,ValidationError + +class PurchaseRFQ(models.Model): + _name = 'annual.rfq' + _description = 'Request for Quotation' + _inherit = ['mail.thread', 'mail.activity.mixin'] + + name = fields.Char(string='Reference Number', default='New', copy=False, tracking=True) + source_request_ref = fields.Many2one( 'odx.annual.request', string='Source Request Reference', help='Original request number/reference (optional)') + user_id = fields.Many2one( + 'res.users', string='Purchase Representative', index=True, tracking=True, + default=lambda self: self.env.user, check_company=True) + vendor_id = fields.Many2one( + 'res.partner', + string='Vendor', + domain=[('supplier_rank', '>', 0)], + ) + partner_id = fields.Many2one( + 'res.partner', + string='Vendor', + + ) + request_date = fields.Date(string='Request Date', default=fields.Date.context_today, tracking=True) + partner_ref = fields.Char(string='Partner Ref') + note = fields.Text(string='Notes') + state = fields.Selection([ + ('draft', 'Sign'), + ('sent', 'Sent to Vendor'), + ('committee', 'Committees'), + ('po', 'Purchase Order'), + ('approved', 'Approved'), + ('rejected', 'Rejected'), + ], string='Status', default='draft', tracking=True) + purpose = fields.Text(string='Purpose') + company_id = fields.Many2one('res.company', default=lambda self: self.env.company, required=True) + currency_id = fields.Many2one('res.currency', string='Currency', + default=lambda self: self.env.company.currency_id.id) + + notes = fields.Text('Terms and Conditions') + line_ids = fields.One2many('annual.rfq.line', 'rfq_id', string='RFQ Lines') + + amount_untaxed = fields.Monetary(string='Untaxed Amount', compute='_compute_amounts', + currency_field='currency_id', store=True) + amount_tax = fields.Monetary(string='Taxes', compute='_compute_amounts', + currency_field='currency_id', store=True) + amount_total = fields.Monetary(string='Total', compute='_compute_amounts', + currency_field='currency_id', store=True) + + attach_no = fields.Integer(string='Documents', compute='_compute_attach_no') + + committe_members = fields.One2many('committe.member', 'rfq_id', string='Committee Members (RFQ)') + + recommendation_order = fields.Boolean(string='Recommend') + purchase_commitee = fields.Boolean( + string='Purchase Committee?', + related='source_request_ref.committee_enabled', + store=False, + readonly=True + ) + source_request_state = fields.Selection( + related='source_request_ref.state', + string='Source Request State', + store=False, readonly=True + ) + show_committee_actions = fields.Boolean( + compute='_compute_show_committee_actions', + store=False + ) + no_of_approve = fields.Integer("No. of Votes ", compute="_compute_no_approve") + technical_attachment_ids = fields.Many2many( + 'ir.attachment', + string="Technical Attachments", + help="Upload technical offer documents here" + ) + @api.depends('committe_members') + def _compute_no_approve(self): + for rec in self: + rec.no_of_approve = len(rec.committe_members) + + + def _compute_show_committee_actions(self): + for rec in self: + rec.show_committee_actions = bool( + rec.source_request_ref + and rec.source_request_state == 'committee' + and rec.purchase_commitee + and rec.state in ('committee', 'draft', 'sent') + ) + + def _get_current_member_vote(self): + self.ensure_one() + return self.env['committe.member'].search([ + ('rfq_id', '=', self.id), + ('user_id', '=', self.env.user.id), + ], limit=1) + def action_recommend(self): + for order in self: + order.recommendation_order = True + + + def action_select_rfq(self): + self.ensure_one() + member = self._get_current_member_vote() + if member: + if member.select: + raise UserError(_("You have already selected this RFQ before.")) + + return { + 'type': 'ir.actions.act_window', + 'name': _('Select Reason'), + 'res_model': 'select.reason.rfq', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_rfq_id': self.id + } + } + + def action_refuse_rfq(self): + self.ensure_one() + member = self._get_current_member_vote() + if member and member.refused: + raise UserError(_("You have already refused this RFQ before.")) + + return { + 'type': 'ir.actions.act_window', + 'name': _('Refuse Reason'), + 'res_model': 'refuse.reason.rfq', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_rfq_id': self.id + } + } + + def action_submit_to_committees(self): + res = super(PurchaseRFQ, self).action_submit_to_committees() + for rec in self: + for user in rec.source_request_ref.committe_members: + existing = self.env['committe.member'].search([ + ('rfq_id', '=', rec.id), + ('user_id', '=', user.id), + ], limit=1) + if not existing: + self.env['committe.member'].create({ + 'rfq_id': rec.id, + 'user_id': user.id, + }) + return res + def _compute_attach_no(self): + for rec in self: + rec.attach_no = self.env['ir.attachment'].search_count([ + ('res_model', '=', 'annual.rfq'), + ('res_id', '=', rec.id) + ]) + + def get_attachments(self): + self.ensure_one() + return { + 'name': 'Attachments', + 'type': 'ir.actions.act_window', + 'res_model': 'ir.attachment', + 'view_mode': 'kanban,tree,form', + 'domain': [('res_model', '=', 'annual.rfq'), ('res_id', '=', self.id)], + 'context': {'default_res_model': 'annual.rfq', 'default_res_id': self.id}, + 'target': 'current', + } + + def action_rfq_send(self): + self.ensure_one() + if not self.vendor_id: + raise UserError(_('Please select a Vendor before sending the Request for Quotation.')) + + self.partner_id = self.vendor_id.id + + try: + template = self.env.ref('odex25_annual_purchase.email_template_annual_rfq') + except ValueError: + template = False + + try: + compose_form = self.env.ref('mail.email_compose_message_wizard_form') + except ValueError: + compose_form = False + + ctx = dict(self.env.context or {}) + ctx.update({ + 'default_model': 'annual.rfq', + 'active_model': 'annual.rfq', + 'active_id': self.id, + 'default_res_id': self.id, + 'default_use_template': bool(template), + 'default_template_id': template.id if template else False, + 'default_composition_mode': 'mass_mail', + 'force_email': True, + 'mail_post_autofollow': False, + 'mail_post_autolog': False, + 'default_notify': False, + 'default_is_log': False, + }) + + lang = self.env.context.get('lang') + if template and template.lang: + lang_map = template._render_lang([self.id]) + lang = lang_map.get(self.id, lang) + self = self.with_context(lang=lang) + + ctx['model_description'] = _('Request for Quotation') + + self.state ='sent' + return { + 'name': _('Compose Email'), + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_model': 'mail.compose.message', + 'views': [(compose_form.id, 'form')], + 'view_id': compose_form.id, + 'target': 'new', + 'context': ctx, + } + + def action_mark_sent(self): + self.write({'state': 'sent'}) + + def _validate_lines_simple(self): + self.ensure_one() + + if not self.line_ids: + raise UserError(_('الرجاء إضافة سطر واحد على الأقل.')) + + if any((l.quantity or 0.0) <= 0.0 for l in self.line_ids): + raise UserError(_('ضروري الكمية تكون أكبر من 0.')) + + if any((l.price_unit or 0.0) <= 0.0 for l in self.line_ids): + raise UserError(_('رجاء أضِف الأسعار لكل السطور.')) + + def _sync_approved_prices_to_request(self): + + self.ensure_one() + req = self.source_request_ref + if not req or not req.line_ids: + return + + rfq_by_product = {} + for l in self.line_ids: + rfq_by_product.setdefault(l.product_id.id, []).append(l) + + def _convert_price(p): + if self.currency_id and req.currency_id and self.currency_id != req.currency_id: + return self.currency_id._convert( + p or 0.0, + req.currency_id, + self.company_id, + fields.Date.context_today(self) + ) + return p or 0.0 + + for rline in req.line_ids.sorted(key=lambda x: x.id): + if not rline.product_id: + continue + bucket = rfq_by_product.get(rline.product_id.id) + if bucket: + rfq_line = bucket.pop(0) + new_price = _convert_price(rfq_line.price_unit) + vals = { + 'price_unit': new_price, + + } + rline.write(vals) + def action_sign_rfq(self): + for rec in self: + rec._validate_lines_simple() + if self.source_request_ref.committee_enabled and self.no_of_approve < self.source_request_ref.min_approve: + raise ValidationError( + _("Sorry You cannot sign this quotation ,YOU NEED MORE COMMITTE MEMBERS TO choose it")) + + if rec.source_request_ref.state in ['committee', 'procurement']: + rec._sync_approved_prices_to_request() + rec.state = 'po' + if rec.source_request_ref.ssd_approve: + rec.source_request_ref.state = 'ssd' + elif rec.source_request_ref.seo_approve: + rec.source_request_ref.state = 'ceo' + else: + rec.source_request_ref.state = 'purchase' + else: + raise UserError(_( + 'Cannot continue: the source document is Approval stage.' + )) + + + + + @api.depends('line_ids.subtotal', 'line_ids.tax_amount') + def _compute_amounts(self): + for rec in self: + untaxed = sum(rec.line_ids.mapped('subtotal')) + tax_amt = sum(rec.line_ids.mapped('tax_amount')) + rec.amount_untaxed = untaxed + rec.amount_tax = tax_amt + rec.amount_total = untaxed + tax_amt + + @api.model + def create(self, vals): + if vals.get('name', 'New') in (False, '/', 'New'): + vals['name'] = self.env['ir.sequence'].next_by_code('annual.rfq') or 'New' + return super().create(vals) + + + def action_approve(self): + for rec in self: + rec.state = 'approved' + + def action_reject(self): + for rec in self: + rec.state = 'rejected' + + +class PurchaseRFQLine(models.Model): + _name = 'annual.rfq.line' + _description = 'RFQ Line Items' + _order = 'sequence, id' + + rfq_id = fields.Many2one('annual.rfq', string='RFQ', required=True, ondelete='cascade') + sequence = fields.Integer(default=10) + product_id = fields.Many2one('product.product', string="Product", required=True) + description = fields.Char(string="Technical Description") + quantity = fields.Float(string="Quantity", default=1.0) + uom_id = fields.Many2one('uom.uom', string="UoM", related='product_id.uom_po_id') + company_id = fields.Many2one(related='rfq_id.company_id', store=True, readonly=True) + currency_id = fields.Many2one(related='rfq_id.currency_id', store=True, readonly=True) + + price_unit = fields.Monetary(string="Unit Price", currency_field='currency_id') + + taxes_id = fields.Many2many('account.tax', string='Taxes', + domain=['|', ('active', '=', False), ('active', '=', True)]) + + # computed totals + subtotal = fields.Monetary(string='Subtotal', compute='_compute_amounts', + store=True, currency_field='currency_id') + tax_amount = fields.Monetary(string='Tax Amount', compute='_compute_amounts', + store=True, currency_field='currency_id') + + @api.depends('quantity', 'price_unit', 'taxes_id', 'currency_id') + def _compute_amounts(self): + for line in self: + qty = line.quantity or 0.0 + price = line.price_unit or 0.0 + if line.taxes_id: + res = line.taxes_id.with_context(force_price_include=False).compute_all( + price, currency=line.currency_id, quantity=qty, + product=line.product_id, partner=line.rfq_id.vendor_id) + line.subtotal = res['total_excluded'] + line.tax_amount = res['total_included'] - res['total_excluded'] + else: + line.subtotal = qty * price + line.tax_amount = 0.0 diff --git a/odex25_purchase/odex25_annual_purchase/models/committe.py b/odex25_purchase/odex25_annual_purchase/models/committe.py new file mode 100644 index 000000000..fa2aedc95 --- /dev/null +++ b/odex25_purchase/odex25_annual_purchase/models/committe.py @@ -0,0 +1,89 @@ +# odx_rfq_committee/models/committee_member_inherit.py +from odoo import models, fields, _, api + +class CommitteMembers(models.Model): + _inherit = "committe.member" + + rfq_id = fields.Many2one('annual.rfq', string="RFQ") + + + + + + + +class SelectReasonRFQ(models.TransientModel): + _name = "select.reason.rfq" + _description = "RFQ Committee Select Reason" + + select_reason = fields.Text("Select Reason", required=True) + rfq_id = fields.Many2one('annual.rfq', string="RFQ", required=True) + + def action_select(self): + self.ensure_one() + rfq = self.rfq_id + + member = self.env['committe.member'].search([ + ('rfq_id', '=', rfq.id), + ('user_id', '=', self.env.user.id), + ], limit=1) + + vals = { + 'selection_reason': self.select_reason, + 'select': True, + 'refused': False, + 'refusing_reason': False, + 'user_id': self.env.user.id, + 'rfq_id': rfq.id, + } + + if member: + member.write(vals) + else: + self.env['committe.member'].create(vals) + + if rfq.source_request_ref and hasattr(rfq.source_request_ref, 'actual_vote'): + rfq.source_request_ref.actual_vote += 1 + + rfq.message_post(body=_("Recommended by %s: %s") % (self.env.user.name, self.select_reason)) + return {'type': 'ir.actions.act_window_close'} + + + + + +class RefuseReasonRFQ(models.TransientModel): + _name = "refuse.reason.rfq" + _description = "RFQ Committee Refuse Reason" + + refuse_reason = fields.Text("Refuse Reason", required=True) + rfq_id = fields.Many2one('annual.rfq', string="RFQ", required=True) + + def action_refuse(self): + self.ensure_one() + rfq = self.rfq_id + + member = self.env['committe.member'].search([ + ('rfq_id', '=', rfq.id), + ('user_id', '=', self.env.user.id), + ], limit=1) + + vals = { + 'refusing_reason': self.refuse_reason, + 'refused': True, + 'select': False, + 'selection_reason': False, + 'user_id': self.env.user.id, + 'rfq_id': rfq.id, + } + + if member: + member.write(vals) + else: + self.env['committe.member'].create(vals) + + if rfq.source_request_ref and hasattr(rfq.source_request_ref, 'actual_vote'): + rfq.source_request_ref.actual_vote += 1 + + rfq.message_post(body=_("تم الرفض من %s: %s") % (self.env.user.name, self.refuse_reason)) + return {'type': 'ir.actions.act_window_close'} diff --git a/odex25_purchase/odex25_annual_purchase/models/purchase_inherit.py b/odex25_purchase/odex25_annual_purchase/models/purchase_inherit.py deleted file mode 100644 index 431d2cf6c..000000000 --- a/odex25_purchase/odex25_annual_purchase/models/purchase_inherit.py +++ /dev/null @@ -1,324 +0,0 @@ -# -*- coding: utf-8 -*- -from odoo import models, fields ,api,_ -from odoo.exceptions import UserError, ValidationError - -class PurchaseOrder(models.Model): - _inherit = 'purchase.order' - - annual_request_id = fields.Many2one('odx.annual.request', string="Annual Request", index=True, ondelete='set null') - department_id = fields.Many2one('hr.department', string="Department") - purpose = fields.Char(string="Purpose") - is_recommended = fields.Boolean(string="Recommended by Committee") - technical_attachment_id = fields.Many2one( - 'ir.attachment', - string="Technical Offer Attachment", - ) - # حقول محسوبة من الاحتياج السنوي - # annual_purchase_commitee = fields.Boolean( - # string='Annual Committee?', - # compute='_compute_annual_committee_fields', - # store=True - # ) - annual_can_committee_vote = fields.Boolean( - compute='_compute_annual_can_committee_vote' - ) - is_technical_committee = fields.Boolean( - compute='_compute_is_technical_committee', - string='Is Technical Committee', - store=False - ) - show_send_purchase_manager = fields.Boolean(compute='_compute_show_send_purchase_manager') - - cancel_reason = fields.Text(string="Cancel Reason") - @api.depends('state', 'requisition_state', 'requisition_type_exclusive','requisition_id', 'is_purchase_budget') - def _compute_show_send_purchase_manager(self): - for record in self: - if ( - (record.state == 'sent' and not record.is_purchase_budget and not record.requisition_id) - or - (record.state == 'wait' and not record.is_purchase_budget and not record.requisition_id) - ): - record.show_send_purchase_manager = False # يظهر الزر - else: - record.show_send_purchase_manager = True # يخفي الزر - - @api.depends( - 'requisition_id', 'requisition_id.state', 'requisition_id.purchase_commitee', - 'annual_request_id', 'annual_request_id.state', 'annual_request_id.committee_enabled' - ) - def _compute_parent_state(self): - """ - - """ - for rec in self: - state = False - comm = False - - if rec.requisition_id: - state = rec.requisition_id.state or False - comm = bool(getattr(rec.requisition_id, 'purchase_commitee', False)) - elif rec.annual_request_id: - state = rec.annual_request_id.state or False - comm = bool(getattr(rec.annual_request_id, 'committee_enabled', False)) - - rec.parent_state = state - rec.purchase_commitee = comm - - def _compute_is_technical_committee(self): - for record in self: - record.is_technical_committee = self.env.user.has_group( - 'odex25_annual_purchase.group_technical_committee' - ) - - - def _compute_annual_can_committee_vote(self): - user = self.env.user - for po in self: - annual_req = po.annual_request_id - if annual_req and annual_req.committee_enabled: - is_member = user in annual_req.committe_members - po.annual_can_committee_vote = annual_req.sent_to_commitee and is_member - else: - po.annual_can_committee_vote = False - - @api.model - def create(self, vals): - res = super(PurchaseOrder, self).create(vals) - if res.annual_request_id and res.annual_request_id.committee_enabled: - res._copy_committee_from_annual_request() - return res - - def _copy_committee_from_annual_request(self): - self.ensure_one() - if not self.annual_request_id: - return - - annual = self.annual_request_id - if not annual.committee_enabled: - return - - self.committe_members.unlink() - - for member in annual.committe_members: - self.env['committe.member'].create({ - 'po_id': self.id, - 'user_id': member.id, - 'select': False, - 'refused': False, - }) - - - @api.constrains('recommendation_order') - def check_recommendation_order(self): - for rec in self: - if not rec.recommendation_order: - continue - - domain = [ - ('id', '!=', rec.id), - ('state', '!=', 'cancel'), - ('recommendation_order', '=', True), - ] - - if rec.requisition_id: - domain.append(('requisition_id', '=', rec.requisition_id.id)) - elif rec.annual_request_id: - domain.append(('annual_request_id', '=', rec.annual_request_id.id)) - else: - continue - if self.env['purchase.order'].search_count(domain): - raise ValidationError(_("Only one recommended order is allowed per parent document.")) - - @api.depends('state', 'requisition_state', 'requisition_type_exclusive', 'requisition_id', 'is_purchase_budget', 'annual_request_id') - def _compute_hide_action_budget_button(self): - for record in self: - # Domain 1 logic - domain1_met = (record.state != 'sign') or \ - (record.state == 'sign' and - record.requisition_state == 'purchase_manager' and - record.requisition_type_exclusive == 'exclusive') - - # Domain 2 logic - domain2_met = (record.has_requisition) or \ - (record.state not in ('wait', 'sent')) or \ - (not record.is_purchase_budget) - - # Combined logic - if (domain1_met and record.has_requisition) or (domain2_met and record.state != 'sign'): - record.hide_action_budget_button = True - else: - record.hide_action_budget_button = False - - if record.state == 'wait' and record.annual_request_id: - record.hide_action_budget_button = True - - - def action_select(self): - if self.annual_request_id and self.annual_request_id.committee_enabled: - self.annual_request_id.actual_vote += 1 - elif self.requisition_id: - self.requisition_id.actual_vote += 1 - - return { - 'type': 'ir.actions.act_window', - 'name': _('Select Reason'), - 'res_model': 'select.reason', - 'view_mode': 'form', - 'target': 'new', - 'context': {'default_order_id': self.id} - } - - - def action_refuse(self): - if self.annual_request_id and self.annual_request_id.committee_enabled: - self.annual_request_id.actual_vote += 1 - elif self.requisition_id: - self.requisition_id.actual_vote += 1 - - return { - 'type': 'ir.actions.act_window', - 'name': _('Refuse Reason'), - 'res_model': 'refuse.reason', - 'view_mode': 'form', - 'target': 'new', - 'context': {'default_order_id': self.id} - } - - def _fill_annual_prices_from_po(self): - - for po in self: - annual = po.annual_request_id - if not annual: - continue - - for rline in annual.line_ids: - pol = po.order_line.filtered(lambda l: l.product_id == rline.product_id)[:1] - if not pol: - continue - - price = pol.price_unit - - if po.currency_id != annual.currency_id: - convert_date = po.date_order.date() if po.date_order else fields.Date.context_today(self) - price = po.currency_id._convert( - price, annual.currency_id, po.company_id, convert_date - ) - - if pol.product_uom and rline.uom_id and pol.product_uom != rline.uom_id: - price = pol.product_uom._compute_price(price, rline.uom_id) - - rline.price_unit = price - - def action_sign(self): - if self.annual_request_id and self.annual_request_id.committee_enabled: - annual = self.annual_request_id - if annual.actual_vote < annual.min_vote: - raise ValidationError(_("Sorry, the minimum number of committee votes is not satisfied")) - if self.no_of_approve < annual.min_approve: - raise ValidationError( - _("Sorry, you cannot sign this quotation. You need more committee members to choose it")) - - if self.annual_request_id: - other_orders = self.env['purchase.order'].search([ - ('annual_request_id', '=', self.annual_request_id.id), - ('id', '!=', self.id) - ]) - for order in other_orders: - order.action_unsign() - self.annual_request_id.vendor_id = self.partner_id.id - self._fill_annual_prices_from_po() - if self.annual_request_id.ssd_approve: - self.annual_request_id.state = 'ssd' - elif self.annual_request_id.seo_approve: - self.annual_request_id.state = 'seo' - else: - self.annual_request_id.state = 'purchase' - - super(PurchaseOrder, self).action_sign() - - - def _compute_can_committee_vote(self): - user = self.env.user - context = self._context or {} - from_committee = context.get('from_committee_action', False) - - for po in self: - requisition = po.requisition_id - annual_request = po.annual_request_id - is_member = requisition and user in requisition.committe_members - po.can_committee_vote = (requisition.sent_to_commitee or annual_request.sent_to_commitee ) - - # bool( - # from_committee and - # requisition and - # requisition.purchase_commitee and - # is_member - # ) - - def _check_requisition_rejection(self): - if self.requisition_id: - PO = self.env['purchase.order'].search([ - ('requisition_id', '=', self.requisition_id.id) - ]) - if PO and all(p.state == 'rejected_by_committee' for p in PO): - self.sudo().requisition_id.state = 'rejected_by_committee' - self.sudo().requisition_id.sent_to_commitee = False - - elif self.annual_request_id: - PO = self.env['purchase.order'].search([ - ('annual_request_id', '=', self.annual_request_id.id) - ]) - if PO and all(p.state == 'rejected_by_committee' for p in PO): - self.sudo().annual_request_id.state = 'rejected_by_committee' - self.sudo().annual_request_id.sent_to_commitee = False - - - - - - - def rejection_committee_head(self): - self.sudo().state = 'rejected_by_committee' - self._check_requisition_rejection() - - def _check_committee_rejection(self): - """ - This method checks two conditions: - 1. If the committee members in the Purchase Requisition are the same as in the Purchase Order. - 2. If all members in the Purchase Order have refused. - If both are true, it changes the PO state. - """ - self.ensure_one() - - if not (self.requisition_id or self.annual_request_id): - return - - requisition_users = self.requisition_id.committe_members - annual_request_users = self.annual_request_id.committe_members - po_users = self.committe_members.mapped('user_id') - if self.requisition_id.committe_members: - if set(requisition_users.ids) == set(po_users.ids): - - if self.committe_members and all(member.refused for member in self.committe_members): - self.write({ - 'state': 'rejected_by_committee', - - }) - self._check_requisition_rejection() - - self.message_post(body=_("تم رفض عرض السعر من قبل جميع أعضاء اللجنة.")) - elif self.annual_request_id.committe_members: - if set(annual_request_users.ids) == set(po_users.ids): - - if self.committe_members and all(member.refused for member in self.committe_members): - self.write({ - 'state': 'rejected_by_committee', - - }) - self._check_requisition_rejection() - - self.message_post(body=_("تم رفض عرض السعر من قبل جميع أعضاء اللجنة.")) - - - - return True \ No newline at end of file diff --git a/odex25_purchase/odex25_annual_purchase/security/ir.model.access.csv b/odex25_purchase/odex25_annual_purchase/security/ir.model.access.csv index e3d9b9cf7..1ac9487bc 100644 --- a/odex25_purchase/odex25_annual_purchase/security/ir.model.access.csv +++ b/odex25_purchase/odex25_annual_purchase/security/ir.model.access.csv @@ -1,6 +1,6 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_request_staff,access_request_staff,model_odx_annual_request,purchase.group_purchase_user,1,1,1,0 -access_request_manager,access_request_manager,model_odx_annual_request,purchase.group_purchase_manager,1,1,1,1 +access_request_manager,access_request_manager,model_odx_annual_request,purchase.group_purchase_manager,1,1,1,0 access_request_committee,access_request_committee,model_odx_annual_request,purchase_requisition_custom.committe_member,1,0,0,0 access_request_ssd,access_request_ssd,model_odx_annual_request,hr_base.group_services_manager,1,1,0,0 access_request_gm,access_request_gm,model_odx_annual_request,hr_base.group_general_manager,1,1,0,0 @@ -8,10 +8,31 @@ access_request_line_staff,access_request_line_staff,model_odx_annual_request_lin access_addendum_staff,access_addendum_staff,model_odx_annual_addendum,purchase.group_purchase_user,1,1,1,0 access_addendum_line_staff,access_addendum_line_staff,model_odx_annual_addendum_line,purchase.group_purchase_user,1,1,1,0 access_odx_annual_request_reason_wizard_user,odx_annual_request_reason_wizard_user,model_odx_annual_request_reason_wizard,base.group_user,1,1,1,1 -access_purchase_order_group_technical_committee,purchase_order_group_technical_committee,model_purchase_order,odex25_annual_purchase.group_technical_committee,1,1,0,0 -access_purchase_order_group_annual_committee,purchase_order_group_annual_committee,model_purchase_order,odex25_annual_purchase.group_annual_committee,1,1,0,0 access_annual_request_group_technical_committee,annual_request_group_technical_committee,model_odx_annual_request,odex25_annual_purchase.group_technical_committee,1,1,0,0 access_annual_request_group_annual_committee,annual_request_group_annual_committee,model_odx_annual_request,odex25_annual_purchase.group_annual_committee,1,1,0,0 access_annual_request_line_group_technical_committee,annual_request_line_group_technical_committee,model_odx_annual_request_line,odex25_annual_purchase.group_technical_committee,1,1,0,0 -access_annual_request_line_group_annual_committee,annual_request_line_group_annual_committee,model_odx_annual_request_line,odex25_annual_purchase.group_annual_committee,1,1,0,0 \ No newline at end of file +access_annual_request_line_group_annual_committee,annual_request_line_group_annual_committee,model_odx_annual_request_line,odex25_annual_purchase.group_annual_committee,1,1,0,0 + + + +access_annual_rfq_purchase_user,access_annual_rfq_purchase_user,model_annual_rfq,purchase.group_purchase_user,1,1,1,0 +access_annual_rfq_purchase_manager,access_annual_rfq_purchase_manager,model_annual_rfq,purchase.group_purchase_manager,1,1,1,0 +access_annual_rfq_services_manager,access_annual_rfq_services_manager,model_annual_rfq,hr_base.group_services_manager,1,1,1,0 +access_annual_rfq_general_manager,access_annual_rfq_general_manager,model_annual_rfq,hr_base.group_general_manager,1,1,1,0 +access_annual_rfq_technical_committee,access_annual_rfq_technical_committee,model_annual_rfq,odex25_annual_purchase.group_technical_committee,1,1,0,0 +access_annual_rfq_annual_committee,access_annual_rfq_annual_committee,model_annual_rfq,odex25_annual_purchase.group_annual_committee,1,1,0,0 + +access_annual_rfq_line_purchase_user,access_annual_rfq_line_purchase_user,model_annual_rfq_line,purchase.group_purchase_user,1,1,1,0 +access_annual_rfq_line_purchase_manager,access_annual_rfq_line_purchase_manager,model_annual_rfq_line,purchase.group_purchase_manager,1,1,1,0 +access_annual_rfq_line_services_manager,access_annual_rfq_line_services_manager,model_annual_rfq_line,hr_base.group_services_manager,1,1,1,0 +access_annual_rfq_line_general_manager,access_annual_rfq_line_general_manager,model_annual_rfq_line,hr_base.group_general_manager,1,1,1,0 +access_annual_rfq_line_technical_committee,access_annual_rfq_line_technical_committee,model_annual_rfq_line,odex25_annual_purchase.group_technical_committee,1,1,0,0 +access_annual_rfq_line_annual_committee,access_annual_rfq_line_annual_committee,model_annual_rfq_line,odex25_annual_purchase.group_annual_committee,1,1,0,0 + +access_select_reason_rfq_user,access.select.reason.rfq.user,model_select_reason_rfq,base.group_user,1,1,1,1 +access_refuse_reason_rfq_user,access.refuse.reason.rfq.user,model_refuse_reason_rfq,base.group_user,1,1,1,1 + + +access_select_reason_rfq,access_select_reason_rfq,model_select_reason_rfq,base.group_user,1,1,1,1 +access_refuse_reason_rfq,access_refuse_reason_rfq,model_refuse_reason_rfq,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/odex25_purchase/odex25_annual_purchase/security/odex25_security.xml b/odex25_purchase/odex25_annual_purchase/security/odex25_security.xml index 5b44e7b79..25734211d 100644 --- a/odex25_purchase/odex25_annual_purchase/security/odex25_security.xml +++ b/odex25_purchase/odex25_annual_purchase/security/odex25_security.xml @@ -19,4 +19,31 @@ To Draft + + + Committee members: see their Annual Requests + + + + ['|', + ('committe_members', 'in', user.id), + ('committe_head', '=', user.id) + ] + + + + Committee members: see their RFQs + + + + + ['|', + ('committe_members.user_id', '=', user.id), + ('source_request_ref.committe_members', 'in', user.id) + ] + + + diff --git a/odex25_purchase/odex25_annual_purchase/views/annual_request_views.xml b/odex25_purchase/odex25_annual_purchase/views/annual_request_views.xml index d700ac8d1..ec940ea38 100644 --- a/odex25_purchase/odex25_annual_purchase/views/annual_request_views.xml +++ b/odex25_purchase/odex25_annual_purchase/views/annual_request_views.xml @@ -159,6 +159,6 @@ search,tree,form [('state' , '=' , 'committee')] {'create':False,'edit':False, 'delete': False, 'duplicate':False, 'from_committee_action': True} - + \ No newline at end of file diff --git a/odex25_purchase/odex25_annual_purchase/views/annual_rfq_views.xml b/odex25_purchase/odex25_annual_purchase/views/annual_rfq_views.xml new file mode 100644 index 000000000..afbc1e518 --- /dev/null +++ b/odex25_purchase/odex25_annual_purchase/views/annual_rfq_views.xml @@ -0,0 +1,204 @@ + + + + + + + + annual.rfq.form + annual.rfq + +
+
+ +
+ + +
+ +
+
+ Request for Quotation +

+ + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + annual.rfq.form.hide.chatter.committee + annual.rfq + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + annual.rfq.tree + annual.rfq + + + + + + + + + + + + + + + Annual RFQs + annual.rfq + tree,form + {} + + + + diff --git a/odex25_purchase/odex25_annual_purchase/views/menu.xml b/odex25_purchase/odex25_annual_purchase/views/menu.xml index 2b583a1e9..8ba625872 100644 --- a/odex25_purchase/odex25_annual_purchase/views/menu.xml +++ b/odex25_purchase/odex25_annual_purchase/views/menu.xml @@ -1,6 +1,12 @@ + + + - - + + \ No newline at end of file diff --git a/odex25_purchase/odex25_annual_purchase/views/purchase_inherit_views.xml b/odex25_purchase/odex25_annual_purchase/views/purchase_inherit_views.xml deleted file mode 100644 index 631d63727..000000000 --- a/odex25_purchase/odex25_annual_purchase/views/purchase_inherit_views.xml +++ /dev/null @@ -1,375 +0,0 @@ - - - purchase.order.form.products.tech_committee - purchase.order - - - - -
-
- - - -
- -
- -
-
- Request for Quotation - Purchase Order -

- - -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
- - - - - purchase.order.form.products.annual_committee - purchase.order - - - - -
-
- - - -
- -
- -
-
- Request for Quotation - Purchase Order -

- - -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -