Merge pull request #5209 from expsa/khazraji_account

multi committe
This commit is contained in:
mohammed-alkhazrji 2025-11-04 05:43:10 -08:00 committed by GitHub
commit 86c414767b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 1160 additions and 851 deletions

View File

@ -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,

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="email_template_annual_rfq" model="mail.template">
<field name="name">Purchase Order: Send RFQ</field>
<field name="model_id" ref="odex25_annual_purchase.model_annual_rfq"/>
<field name="subject">RFQ ${object.name or ''}</field>
<field name="body_html" type="html">
<div style="margin: 0px; padding: 0px;">
<p style="margin: 0px; padding: 0px; font-size: 13px;">
Dear ${object.vendor_id.name}
% if object.vendor_id.parent_id:
(${object.vendor_id.parent_id.name})
% endif
<br/><br/>
Here is in attachment a request for quotation <strong>${object.name}</strong>
% if object.partner_ref:
with reference: ${object.partner_ref}
% endif
from ${object.company_id.name}.
<br/><br/>
If you have any questions, please do not hesitate to contact us.
<br/><br/>
Best regards,
</p>
</div></field>
<field name="report_template" ref="odex25_annual_purchase.action_report_annual_rfq"/>
<field name="report_name">${(object.name or '').replace('/','_')}</field>
<field name="lang">${object.vendor_id.lang}</field>
<field name="auto_delete" eval="True"/>
</record>
</data>
</odoo>

View File

@ -11,4 +11,13 @@
<field name="prefix">AD%(y)s-</field>
<field name="padding">4</field>
</record>
<record id="seq_annual_rfq" model="ir.sequence">
<field name="name">Annual RFQ Sequence</field>
<field name="code">annual.rfq</field>
<field name="prefix">RFQ </field>
<field name="padding">5</field>
<field name="number_next">1</field>
<field name="number_increment">1</field>
</record>
</odoo>

View File

@ -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 "أعضاء اللجنة"

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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'}

View File

@ -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

View File

@ -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
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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_request_staff access_request_staff model_odx_annual_request purchase.group_purchase_user 1 1 1 0
3 access_request_manager access_request_manager model_odx_annual_request purchase.group_purchase_manager 1 1 1 1 0
4 access_request_committee access_request_committee model_odx_annual_request purchase_requisition_custom.committe_member 1 0 0 0
5 access_request_ssd access_request_ssd model_odx_annual_request hr_base.group_services_manager 1 1 0 0
6 access_request_gm access_request_gm model_odx_annual_request hr_base.group_general_manager 1 1 0 0
8 access_addendum_staff access_addendum_staff model_odx_annual_addendum purchase.group_purchase_user 1 1 1 0
9 access_addendum_line_staff access_addendum_line_staff model_odx_annual_addendum_line purchase.group_purchase_user 1 1 1 0
10 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
11 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
12 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
13 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
14 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
15 access_annual_rfq_purchase_user access_annual_rfq_purchase_user model_annual_rfq purchase.group_purchase_user 1 1 1 0
16 access_annual_rfq_purchase_manager access_annual_rfq_purchase_manager model_annual_rfq purchase.group_purchase_manager 1 1 1 0
17 access_annual_rfq_services_manager access_annual_rfq_services_manager model_annual_rfq hr_base.group_services_manager 1 1 1 0
18 access_annual_rfq_general_manager access_annual_rfq_general_manager model_annual_rfq hr_base.group_general_manager 1 1 1 0
19 access_annual_rfq_technical_committee access_annual_rfq_technical_committee model_annual_rfq odex25_annual_purchase.group_technical_committee 1 1 0 0
20 access_annual_rfq_annual_committee access_annual_rfq_annual_committee model_annual_rfq odex25_annual_purchase.group_annual_committee 1 1 0 0
21 access_annual_rfq_line_purchase_user access_annual_rfq_line_purchase_user model_annual_rfq_line purchase.group_purchase_user 1 1 1 0
22 access_annual_rfq_line_purchase_manager access_annual_rfq_line_purchase_manager model_annual_rfq_line purchase.group_purchase_manager 1 1 1 0
23 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
24 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
25 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
26 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
27 access_select_reason_rfq_user access.select.reason.rfq.user model_select_reason_rfq base.group_user 1 1 1 1
28 access_refuse_reason_rfq_user access.refuse.reason.rfq.user model_refuse_reason_rfq base.group_user 1 1 1 1
29 access_select_reason_rfq access_select_reason_rfq model_select_reason_rfq base.group_user 1 1 1 1
30 access_refuse_reason_rfq access_refuse_reason_rfq model_refuse_reason_rfq base.group_user 1 1 1 1
31
32
33
34
35
36
37
38

View File

@ -19,4 +19,31 @@
<field name="name">To Draft</field>
<field name="category_id" ref="module_category_purchase_management"/>
</record>
<record id="rule_annual_request_committee_visibility" model="ir.rule">
<field name="name">Committee members: see their Annual Requests</field>
<field name="model_id" ref="odex25_annual_purchase.model_odx_annual_request"/>
<field name="groups" eval="[(4, ref('odex25_annual_purchase.group_annual_committee')),
(4, ref('odex25_annual_purchase.group_technical_committee'))]"/>
<field name="domain_force">
['|',
('committe_members', 'in', user.id),
('committe_head', '=', user.id)
]
</field>
</record>
<record id="rule_rfq_committee_visibility" model="ir.rule">
<field name="name">Committee members: see their RFQs</field>
<field name="model_id" ref="odex25_annual_purchase.model_annual_rfq"/>
<field name="groups" eval="[(4, ref('odex25_annual_purchase.group_annual_committee')),
(4, ref('odex25_annual_purchase.group_technical_committee'))]"/>
<field name="domain_force">
['|',
('committe_members.user_id', '=', user.id),
('source_request_ref.committe_members', 'in', user.id)
]
</field>
</record>
</odoo>

View File

@ -159,6 +159,6 @@
<field name="view_mode">search,tree,form</field>
<field name="domain">[('state' , '=' , 'committee')]</field>
<field name="context">{'create':False,'edit':False, 'delete': False, 'duplicate':False, 'from_committee_action': True}</field>
<field name="groups_id" eval="[(4, ref('purchase_requisition_custom.committe_member')), (4, ref('odex25_annual_purchase.group_technical_committee'))]"/>
<field name="groups_id" eval="[(4, ref('odex25_annual_purchase.group_annual_committee')), (4, ref('odex25_annual_purchase.group_technical_committee'))]"/>
</record>
</odoo>

View File

@ -0,0 +1,204 @@
<!-- views/annual_rfq_views.xml -->
<odoo>
<!-- شجرة البنود (للـ one2many) -->
<!-- فورم الطلب -->
<record id="view_annual_rfq_form" model="ir.ui.view">
<field name="name">annual.rfq.form</field>
<field name="model">annual.rfq</field>
<field name="arch" type="xml">
<form string="Request for Quotation" create="1" edit="1">
<header>
<button name="action_rfq_send"
type="object"
string="Send by Email"
class="btn-primary"
groups="purchase.group_purchase_manager, purchase.group_purchase_user"
attrs="{'invisible': [('state', '!=', 'draft')]}"
/>
<button name="action_sign_rfq"
type="object"
string="Sign"
groups="purchase.group_purchase_manager, purchase.group_purchase_user"
class="btn-primary"
attrs="{'invisible': [('state', 'not in', ['sent','draft'])]}"
/>
<button name="action_select_rfq"
type="object"
string="Select"
class="oe_highlight"
attrs="{'invisible': [('show_committee_actions','=',False)]}"
/>
<button name="action_refuse_rfq"
type="object"
string="Refuse"
attrs="{'invisible': [('show_committee_actions','=',False)]}"
/>
<button type="object" name="action_recommend"
groups="purchase_requisition_custom.group_select_recommended_offer"
confirm="Are you sure you want to process ?"
attrs="{'invisible':['|',
('recommendation_order','=', True),('purchase_commitee', '=', False)]}" string="Recommended"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,sent,committee,po"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="get_attachments"
type="object"
class="oe_stat_button"
icon="fa-file-text-o">
<field name="attach_no" widget="statinfo" string="Documents"/>
</button>
</div>
<div class="oe_title">
<span class="o_form_label" >Request for Quotation</span>
<h1>
<!-- <field name="priority" widget="priority" class="mr-3"/>-->
<field name="name" readonly="1"/>
</h1>
</div>
<group>
<group>
<field name="show_committee_actions" invisible="1"/>
<field name="recommendation_order"/>
<field name="purchase_commitee" invisible="1"/>
<field name="vendor_id"/>
<field name="partner_ref"/>
<field name="source_request_ref"/>
<field name="no_of_approve"/>
</group>
<group invisible="1" >
<field name="currency_id"/>
<field name="company_id" readonly="1"/>
</group>
</group>
<notebook>
<page string="المنتجات" id="page_product">
<field name="line_ids" context="{'default_currency_id': currency_id, 'default_company_id': company_id}">
<tree editable="bottom" string="RFQ Lines">
<field name="company_id" invisible="1"/>
<field name="sequence" invisible="1"/>
<field name="product_id"/>
<field name="description"/>
<field name="quantity"/>
<field name="uom_id" readonly="1"/>
<field name="price_unit"/>
<field name="taxes_id" widget="many2many_tags" domain="[('type_tax_use','=','purchase'), ('company_id', '=', parent.company_id)]" context="{'default_type_tax_use': 'purchase', 'search_view_ref': 'account.account_tax_view_search'}" options="{'no_create': True}" optional="show"/>
<field name="subtotal" sum="Subtotal"/>
</tree>
</field>
<group class="oe_subtotal_footer oe_right">
<field name="amount_untaxed" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="amount_tax" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<div class="oe_subtotal_footer_separator oe_inline">
<label for="amount_total"/>
</div>
<field name="amount_total" nolabel="1" class="oe_subtotal_footer_separator" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</group>
<field name="notes" class="oe_inline" placeholder="Define your terms and conditions ..."/>
<div class="oe_clear"/>
</page>
<page id="page_committee_members" string="Committe Members"
groups="odex25_annual_purchase.group_technical_committee,odex25_annual_purchase.group_annual_committee"
>
<field name="committe_members">
<tree>
<field name="user_id"/>
<field name="selection_reason"/>
<field name="select"/>
<field name="refused"/>
<field name="refusing_reason"/>
</tree>
</field>
</page>
<page string="معلومات أخرى">
<group>
<field name="user_id"/>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<record id="view_annual_rfq_form_hide_chatter_for_committee" model="ir.ui.view">
<field name="name">annual.rfq.form.hide.chatter.committee</field>
<field name="model">annual.rfq</field>
<field name="inherit_id" ref="odex25_annual_purchase.view_annual_rfq_form"/>
<field name="groups_id" eval="[(6, 0, [ref('odex25_annual_purchase.group_technical_committee')])]"/>
<field name="arch" type="xml">
<xpath expr="//page[@id='page_product']" position="replace">
<page string="المنتجات" id="page_product">
<field name="line_ids" context="{'default_currency_id': currency_id, 'default_company_id': company_id}">
<tree editable="bottom" string="RFQ Lines">
<field name="company_id" invisible="1"/>
<field name="sequence" invisible="1"/>
<field name="product_id"/>
<field name="description"/>
<field name="quantity"/>
<field name="uom_id" readonly="1"/>
</tree>
</field>
<field name="notes" class="oe_inline" placeholder="Define your terms and conditions ..."/>
<div class="oe_clear"/>
</page>
</xpath>
<xpath expr="//notebook" position="inside">
<page string="العرض الفني" id="page_technical_offer">
<group>
<field name="technical_attachment_ids" widget="many2many_binary"
string="Technical Attachments"
help="Upload technical offer documents here"/>
</group>
</page>
</xpath>
<xpath expr="//div[@class='oe_chatter']" position="replace"/>
</field>
</record>
<record id="view_annual_rfq_tree" model="ir.ui.view">
<field name="name">annual.rfq.tree</field>
<field name="model">annual.rfq</field>
<field name="arch" type="xml">
<tree string="Annual RFQs" default_order="id desc">
<field name="name"/>
<field name="vendor_id"/>
<field name="request_date"/>
<field name="amount_total"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="action_annual_rfq" model="ir.actions.act_window">
<field name="name">Annual RFQs</field>
<field name="res_model">annual.rfq</field>
<field name="view_mode">tree,form</field>
<field name="context">{}</field>
</record>
</odoo>

View File

@ -1,6 +1,12 @@
<odoo>
<record id="purchase.menu_procurement_management" model="ir.ui.menu">
<field name="groups_id" eval="[(4, ref('odex25_annual_purchase.group_technical_committee')),
(4, ref('odex25_annual_purchase.group_annual_committee')),
(4, ref('purchase.group_purchase_user')),
(4, ref('purchase.group_purchase_manager'))]"/>
</record>
<menuitem id="menu_odx_root" name="Annual Purchase" sequence="10" parent="purchase.menu_purchase_root"/>
<menuitem id="menu_odx_requests" name="Annual Requests" parent="menu_odx_root" sequence="10" action="action_odx_annual_request" groups="hr_base.group_general_manager,hr_base.group_services_manager,purchase.group_purchase_user,purchase.group_purchase_manager,odex25_annual_purchase.group_annual_committee,odex25_annual_purchase.group_technical_committee,"/>
<menuitem id="menu_odx_annual_committee" name="Annual Committee" parent="menu_odx_root" sequence="11" action="action_annual_committe" groups="purchase_requisition_custom.committe_member,odex25_annual_purchase.group_technical_committee"/>
<menuitem id="menu_odx_requests" name="Annual Requests" parent="menu_odx_root" sequence="10" action="action_odx_annual_request" groups="hr_base.group_general_manager,hr_base.group_services_manager,purchase.group_purchase_user,purchase.group_purchase_manager"/>
<menuitem id="menu_odx_annual_committee" name="Annual Committee" parent="menu_odx_root" sequence="11" action="action_annual_committe" groups="odex25_annual_purchase.group_annual_committee,odex25_annual_purchase.group_technical_committee"/>
<!-- <menuitem id="menu_odx_addendum" name="Addendum" parent="menu_odx_root" sequence="30" action="action_odx_annual_addendum"/>-->
</odoo>

View File

@ -1,375 +0,0 @@
<odoo>
<record id="view_po_form_products_for_tech_committee" model="ir.ui.view">
<field name="name">purchase.order.form.products.tech_committee</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase_requisition_custom.purchase_order_custom_form_view"/>
<field name="groups_id" eval="[(4, ref('odex25_annual_purchase.group_technical_committee'))]"/>
<field name="arch" type="xml">
<xpath expr="/form" position="replace">
<form>
<header>
<field name="can_committee_vote" invisible="1"/>
<field name="purchase_commitee" invisible="1"/>
<field name="requisition_id" invisible="1"/>
<button type="object" name="action_select" groups="odex25_annual_purchase.group_technical_committee"
attrs="{'invisible':['|', ('state','in',['done','purchase','waiting','cancel']),('can_committee_vote', '=', False)]}"
string="Select"/>
<button type="object" name="action_refuse" groups="odex25_annual_purchase.group_technical_committee"
attrs="{'invisible':['|',('state','in',['done','purchase','waiting','cancel']),('can_committee_vote', '=', False)]}"
string="Refuse"/>
<field name="state" widget="statusbar" statusbar_visible="draft,sent,purchase" readonly="1"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="get_attachments" type="object"
class="oe_stat_button"
icon="fa-file-text-o">
<field name="attach_no" widget="statinfo" string="Documents"/>
</button>
</div>
<div class="oe_title">
<span class="o_form_label" attrs="{'invisible': [('state','not in',('draft','sent'))]}">Request for Quotation </span>
<span class="o_form_label" attrs="{'invisible': [('state','in',('draft','sent'))]}">Purchase Order </span>
<h1>
<field name="priority" widget="priority" class="mr-3"/>
<field name="name" readonly="1"/>
</h1>
</div>
<group>
<field name="recommendation_order" readonly="1" attrs="{
'invisible': [('purchase_commitee', '=', False)]}"/>
<field name="partner_id" />
<field name="partner_ref" />
<field name="annual_request_id" readonly="1"/>
<field name="no_of_approve"/>
</group>
<notebook>
<page string="Products" name="products">
<field name="order_line"
widget="section_and_note_one2many"
mode="tree"
context="{'default_state': 'draft'}"
attrs="{'readonly': [('state','in',('done','cancel'))]}">
<tree string="عناصر الطلب (لجنة تقنية)" editable="bottom">
<field name="display_type" invisible="1"/>
<field name="currency_id" invisible="1"/>
<field name="state" invisible="1"/>
<field name="product_type" invisible="1"/>
<field name="sequence" widget="handle"/>
<field name="product_id"
attrs="{
'readonly': [('state','in',('purchase','to approve','done','cancel'))],
'required': [('display_type','=',False)]
}"
context="{'partner_id':parent.partner_id, 'quantity':product_qty,'uom':product_uom, 'company_id': parent.company_id}"
force_save="1"
domain="[('purchase_ok','=',True),'|',('company_id','=',False),('company_id','=',parent.company_id)]"/>
<field name="name" widget="section_and_note_text" string="Technical Description"/>
<field name="product_qty"
attrs="{'readonly':[('state','in',('purchase','done','cancel'))]}"/>
<field name="product_uom"
string="UoM"
groups="uom.group_uom"
attrs="{
'readonly':[('state','in',('purchase','done','cancel'))],
'required':[('display_type','=',False)]
}"
force_save="1"/>
<field name="product_uom_category_id" invisible="1"/>
</tree>
<form string="عنصر طلب الشراء">
<field name="state" invisible="1"/>
<field name="display_type" invisible="1"/>
<group attrs="{'invisible':[('display_type','!=',False)]}">
<group>
<field name="product_uom_category_id" invisible="1"/>
<field name="product_id"
context="{'partner_id': parent.partner_id}"
widget="many2one_barcode"
domain="[('purchase_ok','=',True),'|',('company_id','=',False),('company_id','=',parent.company_id)]"/>
<label for="product_qty"/>
<div class="o_row">
<field name="product_qty"/>
<field name="product_uom" groups="uom.group_uom"
attrs="{'required':[('display_type','=',False)]}"/>
</div>
</group>
</group>
<group>
<field name="name"/>
</group>
<label for="name" string="اسم القسم"
attrs="{'invisible':[('display_type','!=','line_section')]}"/>
<label for="name" string="ملاحظة"
attrs="{'invisible':[('display_type','!=','line_note')]}"/>
<field name="name" nolabel="1"
attrs="{'invisible':[('display_type','=',False)]}"/>
</form>
</field>
</page>
<page id="page_committee_members" string="Committee Members"
groups="odex25_annual_purchase.group_technical_committee"
attrs="{'invisible':['&amp;',('requisition_id','=',False),('annual_request_id','=',False)]}">
<field name="committe_members">
<tree>
<field name="user_id"/>
<field name="selection_reason"/>
<field name="select"/>
<field name="refused"/>
<field name="refusing_reason"/>
</tree>
</field>
</page>
<page string="Other Information" name="purchase_delivery_invoice">
<group>
<group name="other_info">
<field name="user_id" domain="[('share', '=', False)]" widget="many2one_avatar_user"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
</group>
<group name="invoice_info">
<field name="invoice_status" attrs="{'invisible': [('state', 'in', ('draft', 'sent', 'to approve', 'cancel'))]}"/>
<field name="payment_term_id" attrs="{'readonly': ['|', ('invoice_status','=', 'invoiced'), ('state', '=', 'done')]}" options="{'no_create': True}"/>
<field name="fiscal_position_id" options="{'no_create': True}" attrs="{'readonly': ['|', ('invoice_status','=', 'invoiced'), ('state', '=', 'done')]}"/>
</group>
</group>
</page>
<page string="Technical Offer Attachment">
<field name="technical_attachment_id"/>
</page>
</notebook>
</sheet>
</form>
</xpath>
</field>
</record>
<record id="view_po_form_products_for_annual_committee" model="ir.ui.view">
<field name="name">purchase.order.form.products.annual_committee</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase_requisition_custom.purchase_order_custom_form_view"/>
<field name="groups_id" eval="[(4, ref('odex25_annual_purchase.group_annual_committee'))]"/>
<field name="arch" type="xml">
<xpath expr="/form" position="replace">
<form>
<header>
<field name="can_committee_vote" invisible="1"/>
<field name="purchase_commitee" invisible="1"/>
<field name="requisition_id" invisible="1"/>
<button type="object" name="action_recommend"
groups="purchase_requisition_custom.group_select_recommended_offer"
confirm="Are you sure you want to process ?"
attrs="{'invisible':['|',
('recommendation_order','=', True),('purchase_commitee', '=', False)]}" string="Recommended"/>
<button type="object" name="action_select" groups="odex25_annual_purchase.group_annual_committee"
attrs="{'invisible':['|', ('state','in',['done','purchase','waiting','cancel']),('can_committee_vote', '=', False)]}"
string="Select"/>
<button type="object" name="action_refuse" groups="odex25_annual_purchase.group_annual_committee"
attrs="{'invisible':['|',('state','in',['done','purchase','waiting','cancel']),('can_committee_vote', '=', False)]}"
string="Refuse"/>
<field name="state" widget="statusbar" statusbar_visible="draft,sent,purchase" readonly="1"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="get_attachments" type="object"
class="oe_stat_button"
icon="fa-file-text-o">
<field name="attach_no" widget="statinfo" string="Documents"/>
</button>
</div>
<div class="oe_title">
<span class="o_form_label" attrs="{'invisible': [('state','not in',('draft','sent'))]}">Request for Quotation </span>
<span class="o_form_label" attrs="{'invisible': [('state','in',('draft','sent'))]}">Purchase Order </span>
<h1>
<field name="priority" widget="priority" class="mr-3"/>
<field name="name" readonly="1"/>
</h1>
</div>
<group>
<field name="recommendation_order" readonly="1" attrs="{
'invisible': [('purchase_commitee', '=', False)]}"/>
<field name="partner_id" />
<field name="partner_ref" />
<field name="annual_request_id" readonly="1"/>
<field name="no_of_approve"/>
</group>
<notebook>
<page string="Products" name="products">
<field name="order_line"
widget="section_and_note_one2many"
mode="tree,kanban"
context="{'default_state': 'draft'}"
attrs="{'readonly': [('state', 'in', ('done', 'cancel'))]}">
<tree string="Purchase Order Lines" editable="bottom">
<control>
<create name="add_product_control" string="Add a product"/>
<create name="add_section_control" string="Add a section" context="{'default_display_type': 'line_section'}"/>
<create name="add_note_control" string="Add a note" context="{'default_display_type': 'line_note'}"/>
</control>
<field name="display_type" invisible="1"/>
<field name="currency_id" invisible="1"/>
<field name="state" invisible="1" readonly="1"/>
<field name="product_type" invisible="1"/>
<field name="product_uom_category_id" invisible="1"/>
<field name="invoice_lines" invisible="1"/>
<field name="sequence" widget="handle"/>
<field
name="product_id"
attrs="{
'readonly': [('state', 'in', ('purchase', 'to approve','done', 'cancel'))],
'required': [('display_type', '=', False)],
}"
context="{'partner_id':parent.partner_id, 'quantity':product_qty,'uom':product_uom, 'company_id': parent.company_id}"
force_save="1" domain="[('purchase_ok', '=', True), '|', ('company_id', '=', False), ('company_id', '=', parent.company_id)]"/>
<field name="name" widget="section_and_note_text" string="Technical Description"/>
<field name="product_qty"/>
<field name="product_uom" string="UoM" groups="uom.group_uom"
attrs="{
'readonly': [('state', 'in', ('purchase', 'done', 'cancel'))],
'required': [('display_type', '=', False)]
}"
force_save="1" optional="show"/>
<field name="price_unit" attrs="{'readonly': [('invoice_lines', '!=', [])]}"/>
<field name="taxes_id" widget="many2many_tags" domain="[('type_tax_use','=','purchase'), ('company_id', '=', parent.company_id)]" context="{'default_type_tax_use': 'purchase', 'search_view_ref': 'account.account_tax_view_search'}" options="{'no_create': True}" optional="show"/>
<field name="price_subtotal" widget="monetary"/>
<field name="price_total" invisible="1"/>
<field name="price_tax" invisible="1"/>
</tree>
<form string="Purchase Order Line">
<field name="state" invisible="1"/>
<field name="display_type" invisible="1"/>
<group attrs="{'invisible': [('display_type', '!=', False)]}">
<group>
<field name="product_uom_category_id" invisible="1"/>
<field name="product_id"
context="{'partner_id': parent.partner_id}"
widget="many2one_barcode"
domain="[('purchase_ok', '=', True), '|', ('company_id', '=', False), ('company_id', '=', parent.company_id)]"
/>
<label for="product_qty"/>
<div class="o_row">
<field name="product_qty"/>
<field name="product_uom" groups="uom.group_uom" attrs="{'required': [('display_type', '=', False)]}"/>
</div>
<field name="qty_received_method" invisible="1"/>
<field name="price_unit"/>
<field name="taxes_id" widget="many2many_tags" domain="[('type_tax_use', '=', 'purchase'), ('company_id', '=', parent.company_id)]" options="{'no_create': True}"/>
</group>
<group colspan="12">
<notebook>
<page string="Notes" name="notes">
<field name="name"/>
</page>
<page string="Invoices and Incoming Shipments" name="invoices_incoming_shiptments">
<field name="invoice_lines"/>
</page>
</notebook>
</group>
</group>
<label for="name" string="Section Name (eg. Products, Services)" attrs="{'invisible': [('display_type', '!=', 'line_section')]}"/>
<label for="name" string="Note" attrs="{'invisible': [('display_type', '!=', 'line_note')]}"/>
<field name="name" nolabel="1" attrs="{'invisible': [('display_type', '=', False)]}"/>
</form>
</field>
<group class="oe_subtotal_footer oe_right">
<field name="amount_untaxed" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="amount_tax" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<div class="oe_subtotal_footer_separator oe_inline">
<label for="amount_total"/>
</div>
<field name="amount_total" nolabel="1" class="oe_subtotal_footer_separator" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</group>
<field name="notes" class="oe_inline" placeholder="Define your terms and conditions ..."/>
<div class="oe_clear"/>
</page>
<page id="page_committee_members" string="Committee Members"
groups="odex25_annual_purchase.group_annual_committee"
attrs="{'invisible':['&amp;',('requisition_id','=',False),('annual_request_id','=',False)]}">
<field name="committe_members">
<tree>
<field name="user_id"/>
<field name="selection_reason"/>
<field name="select"/>
<field name="refused"/>
<field name="refusing_reason"/>
</tree>
</field>
</page>
<page string="Other Information" name="purchase_delivery_invoice">
<group>
<group name="other_info">
<field name="user_id" domain="[('share', '=', False)]" widget="many2one_avatar_user"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
</group>
<group name="invoice_info">
<field name="invoice_status" attrs="{'invisible': [('state', 'in', ('draft', 'sent', 'to approve', 'cancel'))]}"/>
<field name="payment_term_id" attrs="{'readonly': ['|', ('invoice_status','=', 'invoiced'), ('state', '=', 'done')]}" options="{'no_create': True}"/>
<field name="fiscal_position_id" options="{'no_create': True}" attrs="{'readonly': ['|', ('invoice_status','=', 'invoiced'), ('state', '=', 'done')]}"/>
</group>
</group>
</page>
</notebook>
</sheet>
</form>
</xpath>
</field>
</record>
<!-- Inherit the Purchase Order form -->
<record id="purchase_order_custom_form_view" model="ir.ui.view">
<field name="name">purchase.order.form.custom</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_form"/>
<field name="priority" eval="100"/>
<field name="arch" type="xml">
<xpath expr="//form/header" position="inside">
<field name="requisition_id" invisible="1"/>
<field name="annual_request_id" invisible="1"/>
<field name="is_signed" invisible="1"/>
</xpath>
<xpath expr="/form/header/button[@name='print_quotation']" position="after">
<button type="object"
name="action_sign"
class="oe_highlight"
string="Sign"
groups="purchase_requisition_custom.group_sign_purchase_order"
attrs="{
'invisible': [
'|','|',
'&amp;', ('requisition_id','=',False), ('annual_request_id','=',False),
('state','in',['sign','purchase','to approve','done','cancel','budget_rejected','wait_for_send','waiting']),
('is_signed','=',True)
]
}"/>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- مستند التقرير (مشابه لِ purchase.report_purchasequotation_document) -->
<template id="report_annual_rfq_document">
<t t-call="web.external_layout">
<t t-set="o" t-value="o.with_context(lang=o.partner_id.lang)"/>
<div style="margin-top:50px"></div>
<t t-set="address">
<strong style="display:block;"> المورد</strong> <strong style="display:block;">Vendor</strong>
<div t-field="o.partner_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True, "phone_icons": True}'/>
<p t-if="o.partner_id.vat"><t t-esc="o.company_id.country_id.vat_label or 'Tax ID'"/>: <span t-field="o.partner_id.vat"/></p>
</t>
<t t-set="information_block">
<div class="company_address mt8">
<div>
<strong style="display:block;">الشركة</strong>
<strong style="display:block;">Company</strong>
<strong t-field="o.company_id.partner_id.name"/>
</div>
<span t-field="o.company_id.partner_id"
t-options='{"widget": "contact", "fields": ["address"], "no_marker": true}'/>
</div>
</t>
<div class="page">
<div class="oe_structure"/>
<h2>طلب عرض سعر</h2>
<h2>#<span t-field="o.name"/></h2>
<table class="table table-sm">
<thead style="display: table-row-group">
<tr>
<th name="th_item" class="text-left">
<span style="display:block;">item</span>
<span style="display:block;">المنتج</span></th>
<th name="th_description" class="text-center">
<span style="display:block;">Description</span>
<span style="display:block;">الوصف</span></th>
<th name="th_quantity" class="text-center">
<span style="display:block;">Quantity</span>
<span style="display:block;">الكمية</span></th>
</tr>
</thead>
<tbody>
<t t-foreach="o.line_ids" t-as="order_line">
<tr t-att-class="'bg-200 font-weight-bold o_line_section'">
<td id="product" class="text-left">
<span t-field="order_line.product_id.name"/>
</td>
<td id="description" class="text-center">
<span t-field="order_line.description"/>
</td>
<td class="text-center">
<span t-field="order_line.quantity"/>
</td>
</tr>
</t>
</tbody>
</table>
<p style="margin-top:50px"><h3>• شروط وأحكام</h3></p>
<p t-esc="o.notes or ''"/>
<div class="oe_structure"/>
</div>
</t>
</template>
<!-- الغلاف (يمر على docs ويستدعي المستند مع لغة المورد) -->
<template id="report_annual_rfq">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="odex25_annual_purchase.report_annual_rfq_document" t-lang="o.vendor_id.lang"/>
</t>
</t>
</template>
<!-- إجراء التقرير -->
<record id="action_report_annual_rfq" model="ir.actions.report">
<field name="name">Annual RFQ</field>
<field name="model">annual.rfq</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">odex25_annual_purchase.report_annual_rfq</field>
<field name="report_file">odex25_annual_purchase.report_annual_rfq</field>
<field name="print_report_name">(object.name)</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,35 @@
<!-- odx_rfq_committee/wizards/select_reason_rfq_views.xml -->
<odoo>
<record id="view_select_reason_rfq_form" model="ir.ui.view">
<field name="name">select.reason.rfq.form</field>
<field name="model">select.reason.rfq</field>
<field name="arch" type="xml">
<form string="Provide Recommendation">
<group>
<field name="rfq_id" invisible="1"/>
<field name="select_reason"/>
</group>
<footer>
<button string="Submit" type="object" name="action_select" class="btn-primary"/>
<button string="Cancel" special="cancel" class="btn-secondary"/>
</footer>
</form>
</field>
</record>
<record id="view_refuse_reason_rfq_form" model="ir.ui.view">
<field name="name">refuse.reason.rfq.form</field>
<field name="model">refuse.reason.rfq</field>
<field name="arch" type="xml">
<form string="Provide Refuse Reason">
<group>
<field name="rfq_id" invisible="1"/>
<field name="refuse_reason"/>
</group>
<footer>
<button string="Submit" type="object" name="action_refuse" class="btn-primary"/>
<button string="Cancel" special="cancel" class="btn-secondary"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@ -18,11 +18,7 @@ class AnnualRequestReasonWizard(models.TransientModel):
req = self.request_id
if self.action_type == 'cancel':
# نفس منطقك الحالي لإلغاء أوامر الشراء المرتبطة
related_orders = self.env['purchase.order'].search([('annual_request_id', '=', req.id)])
for order in related_orders:
if order.state:
order.button_cancel()
req.message_post(body=_("Reason: %s") % self.reason)
vals = {'state': 'cancel'}
if 'cancel_reason' in req._fields: