v3-annual

This commit is contained in:
mohammed-alkhazrji 2025-10-29 23:52:26 +03:00
parent 767f0048a4
commit a2f3f75ea9
6 changed files with 218 additions and 45 deletions

View File

@ -9,8 +9,10 @@ class AnnualAddendum(models.Model):
_order = "create_date desc" _order = "create_date desc"
name = fields.Char(string="Reference", default=lambda self: self.env['ir.sequence'].next_by_code('odx.annual.addendum'), readonly=True, copy=False) name = fields.Char(string="Reference", default=lambda self: self.env['ir.sequence'].next_by_code('odx.annual.addendum'), readonly=True, copy=False)
agreement_id = fields.Many2one('purchase.requisition', string="Agreement", required=True, domain=[('type_id.code','=','blanket_order')]) agreement_id = fields.Many2one('purchase.requisition', string="Agreement", required=True)
vendor_id = fields.Many2one(related='agreement_id.vendor_id', store=True, readonly=True) annual_request_id = fields.Many2one('odx.annual.request', string="Annual Request", index=True, ondelete='cascade')
vendor_id = fields.Many2one(related='annual_request_id.vendor_id', store=True, readonly=True)
department_id = fields.Many2one('hr.department', string="Department") department_id = fields.Many2one('hr.department', string="Department")
purpose = fields.Char(string="Purpose") purpose = fields.Char(string="Purpose")
note = fields.Text(string="Notes") note = fields.Text(string="Notes")
@ -23,6 +25,9 @@ class AnnualAddendum(models.Model):
('cancel','Cancelled'), ('cancel','Cancelled'),
], default='draft', tracking=True) ], default='draft', tracking=True)
line_ids = fields.One2many('odx.annual.addendum.line', 'addendum_id', string="Lines") line_ids = fields.One2many('odx.annual.addendum.line', 'addendum_id', string="Lines")
ssd_approve = fields.Boolean(string="SSD Approve", default=False)
seo_approve = fields.Boolean(string="SEO Approve", default=False)
recommendation = fields.Boolean(string="Recommendation", default=False)
def _check_lines(self): def _check_lines(self):
for rec in self: for rec in self:
@ -69,6 +74,43 @@ class AnnualAddendumLine(models.Model):
product_id = fields.Many2one('product.product', string="Product", required=True, domain=[('purchase_ok','=',True)]) product_id = fields.Many2one('product.product', string="Product", required=True, domain=[('purchase_ok','=',True)])
description = fields.Char(string="Description") description = fields.Char(string="Description")
quantity = fields.Float(string="Quantity", default=1.0) quantity = fields.Float(string="Quantity", default=1.0)
uom_id = fields.Many2one('uom.uom', string="UoM", related='product_id.uom_po_id', readonly=False) uom_id = fields.Many2one('uom.uom', string="UoM", related='product_id.uom_po_id')
price_unit = fields.Monetary(string="Unit Price", currency_field='currency_id', groups="purchase_requisition_custom.committe_member") price_unit = fields.Monetary(string="Unit Price", currency_field='currency_id')
currency_id = fields.Many2one('res.currency', related='addendum_id.agreement_id.company_id.currency_id', readonly=True, store=True) currency_id = fields.Many2one(
'res.currency',
related='addendum_id.agreement_id.company_id.currency_id',
readonly=True, store=True
)
addendum_company_id = fields.Many2one(
'res.company',
related='addendum_id.agreement_id.company_id',
readonly=True, store=True
)
taxes_id = fields.Many2many(
'account.tax', string="Taxes",
domain="[('type_tax_use','=','purchase'), ('company_id','=', addendum_company_id)]"
)
price_subtotal = fields.Monetary(
string="Subtotal", currency_field='currency_id',
compute='_compute_amounts', store=True, readonly=True
)
@api.depends('quantity', 'price_unit', 'taxes_id', 'currency_id', 'product_id')
def _compute_amounts(self):
for rec in self:
qty = rec.quantity or 0.0
price_unit = rec.price_unit or 0.0
currency = rec.currency_id
taxes = rec.taxes_id
if rec.addendum_company_id:
taxes = taxes.filtered(lambda t: t.company_id == rec.addendum_company_id)
res = taxes.compute_all(
price_unit,
currency=currency,
quantity=qty,
product=rec.product_id,
partner=None
)
rec.price_subtotal = res['total_excluded']

View File

@ -35,8 +35,8 @@ class AnnualPurchaseRequest(models.Model):
note = fields.Text(string="Notes") note = fields.Text(string="Notes")
state = fields.Selection(selection=STATES, default='draft', tracking=True) state = fields.Selection(selection=STATES, default='draft', tracking=True)
line_ids = fields.One2many('odx.annual.request.line', 'request_id', string="Products") line_ids = fields.One2many('odx.annual.request.line', 'request_id', string="Products")
agreement_id = fields.Many2one('purchase.requisition', string="Purchase Agreement", readonly=True, tracking=True) agreement_id = fields.Many2one('purchase.requisition', string="Purchase Agreement", readonly=True)
vendor_id = fields.Many2one('res.partner', string="Selected Vendor", domain=[('supplier_rank','>',0)], tracking=True) vendor_id = fields.Many2one('res.partner', string="Selected Vendor", domain=[('supplier_rank','>',0)])
currency_id = fields.Many2one('res.currency', string="Currency", default=lambda self: self.env.company.currency_id.id) currency_id = fields.Many2one('res.currency', string="Currency", default=lambda self: self.env.company.currency_id.id)
technical_notes = fields.Html(string="Technical Notes") technical_notes = fields.Html(string="Technical Notes")
financial_notes = fields.Html(string="Financial Notes", groups="purchase_requisition_custom.committe_member") financial_notes = fields.Html(string="Financial Notes", groups="purchase_requisition_custom.committe_member")
@ -46,13 +46,13 @@ class AnnualPurchaseRequest(models.Model):
('rejected','Rejected by Committee'), ('rejected','Rejected by Committee'),
('recommended','Recommended'), ('recommended','Recommended'),
('approved','Approved by Committee'), ('approved','Approved by Committee'),
], default='none', tracking=True) ], default='none')
product_category_ids = fields.Many2many('product.category', string='Items Categories', product_category_ids = fields.Many2many('product.category', string='Items Categories',
compute='_compute_product_category_ids', compute='_compute_product_category_ids',
store=True) store=True)
committee_enabled = fields.Boolean(string="Require Committee Review", default=False, tracking=True) committee_enabled = fields.Boolean(string="Require Committee Review", default=False)
ssd_approve = fields.Boolean(string="SSD Approve", default=False) ssd_approve = fields.Boolean(string="SSD Approve", default=False)
seo_approve = fields.Boolean(string="SEO Approve", default=False) seo_approve = fields.Boolean(string="SEO Approve", default=False)
@ -78,33 +78,71 @@ class AnnualPurchaseRequest(models.Model):
store=False, store=False,
readonly=True, readonly=True,
) )
addendum_count = fields.Integer(string='Addendums', compute='_compute_addendum_count', readonly=True)
def _compute_addendum_count(self):
Addendum = self.env['odx.annual.addendum']
for rec in self:
rec.addendum_count = Addendum.search_count([('annual_request_id', '=', rec.id)])
def action_open_addendums(self):
self.ensure_one()
domain = [('annual_request_id', '=', self.id)]
return {
'name': _('Addendums'),
'type': 'ir.actions.act_window',
'res_model': 'odx.annual.addendum',
'view_mode': 'tree,form',
'domain': domain,
'context': {
'default_annual_request_id': self.id,
'default_agreement_id': self.agreement_id.id if self.agreement_id else False,
'default_department_id': self.department_id.id if self.department_id else False,
'default_purpose': self.purpose or False,
},
'target': 'current',
}
def _compute_attach_no(self): def _compute_attach_no(self):
Attachment = self.env['ir.attachment'] Attachment = self.env['ir.attachment']
for rec in self: for rec in self:
rec.attach_no = Attachment.search_count([ count_self = Attachment.search_count([
('res_model', '=', rec._name), ('res_model', '=', rec._name),
('res_id', '=', rec.id) ('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
def get_attachments(self): def get_attachments(self):
self.ensure_one() 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)
]
return { return {
'name': "Documents", 'name': _("Documents"),
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
'res_model': 'ir.attachment', 'res_model': 'ir.attachment',
'view_mode': 'kanban,tree,form', 'view_mode': 'kanban,tree,form',
'domain': [ 'domain': domain,
('res_model', '=', self._name),
('res_id', 'in', self.ids)
],
'context': { 'context': {
'default_res_model': self._name, 'default_res_model': self._name,
'default_res_id': self.id, 'default_res_id': self.id,
}, },
'target': 'current', 'target': 'current',
} }
def copy(self, default=None): def copy(self, default=None):
default = dict(default or {}) default = dict(default or {})
@ -219,6 +257,7 @@ class AnnualPurchaseRequest(models.Model):
self._check_lines() self._check_lines()
order_line_vals = [] order_line_vals = []
for line in self.line_ids: for line in self.line_ids:
line.sudo()
order_line_vals.append((0, 0, { order_line_vals.append((0, 0, {
'product_id': line.product_id.id, 'product_id': line.product_id.id,
'name': line.description or line.product_id.description_purchase or line.product_id.name, 'name': line.description or line.product_id.description_purchase or line.product_id.name,
@ -294,13 +333,21 @@ class AnnualPurchaseRequest(models.Model):
if manager.user_id.id == rec.env.uid : if manager.user_id.id == rec.env.uid :
rec.write({'state': 'procurement'}) rec.write({'state': 'procurement'})
else: else:
raise Warning(_("Sorry, The Approval For The Direct Manager '%s' Only !")%(rec.employee_id.parent_id.name)) raise UserError(_("Sorry, The Approval For The Direct Manager '%s' Only !")%(rec.employee_id.parent_id.name))
else: else:
rec.write({'state': 'procurement'}) rec.write({'state': 'procurement'})
def action_manager_reject(self, reason=False): def action_manager_reject(self, reason=False):
self.message_post(body=_("Rejected by Manager: %s") % (reason or '')) for rec in self:
self.write({'state': 'rejected'}) manager = rec.sudo().employee_id.parent_id
if manager:
if manager.user_id.id == rec.env.uid :
rec.write({'state': 'rejected'})
else:
raise UserError(_("Sorry, The Approval For The Direct Manager '%s' Only !")%(rec.employee_id.parent_id.name))
else:
rec.write({'state': 'rejected'})
def action_send_to_committee(self): def action_send_to_committee(self):
self.write({'sent_to_commitee': True}) self.write({'sent_to_commitee': True})
@ -381,10 +428,10 @@ class AnnualPurchaseRequestLine(models.Model):
_description = "Annual Purchase Request Line" _description = "Annual Purchase Request Line"
request_id = fields.Many2one('odx.annual.request', string="Request", required=True, ondelete='cascade') request_id = fields.Many2one('odx.annual.request', string="Request", required=True, ondelete='cascade')
product_id = fields.Many2one('product.product', string="Product", required=True, domain=[('purchase_ok','=',True)]) product_id = fields.Many2one('product.product', string="Product", required=True)
description = fields.Char(string="Technical Description") description = fields.Char(string="Technical Description")
quantity = fields.Float(string="Quantity", default=1.0) quantity = fields.Float(string="Quantity", default=1.0)
uom_id = fields.Many2one('uom.uom', string="UoM", related='product_id.uom_po_id', readonly=False) uom_id = fields.Many2one('uom.uom', string="UoM", related='product_id.uom_po_id')
price_unit = fields.Monetary(string="Unit Price", currency_field='currency_id', groups="purchase_requisition_custom.committe_member") price_unit = fields.Monetary(string="Unit Price", currency_field='currency_id')
currency_id = fields.Many2one('res.currency', related='request_id.currency_id', readonly=True, store=True) currency_id = fields.Many2one('res.currency', related='request_id.currency_id', readonly=True, store=True)
technical_spec = fields.Text(string="Technical Specification") technical_spec = fields.Text(string="Technical Specification")

View File

@ -9,13 +9,16 @@ class PurchaseOrder(models.Model):
department_id = fields.Many2one('hr.department', string="Department") department_id = fields.Many2one('hr.department', string="Department")
purpose = fields.Char(string="Purpose") purpose = fields.Char(string="Purpose")
is_recommended = fields.Boolean(string="Recommended by Committee") is_recommended = fields.Boolean(string="Recommended by Committee")
technical_attachment_id = fields.Many2one(
# حقول محسوبة من الاحتياج السنوي 'ir.attachment',
annual_purchase_commitee = fields.Boolean( string="Technical Offer Attachment",
string='Annual Committee?',
compute='_compute_annual_committee_fields',
store=True
) )
# حقول محسوبة من الاحتياج السنوي
# annual_purchase_commitee = fields.Boolean(
# string='Annual Committee?',
# compute='_compute_annual_committee_fields',
# store=True
# )
annual_can_committee_vote = fields.Boolean( annual_can_committee_vote = fields.Boolean(
compute='_compute_annual_can_committee_vote' compute='_compute_annual_can_committee_vote'
) )
@ -24,7 +27,20 @@ class PurchaseOrder(models.Model):
string='Is Technical Committee', string='Is Technical Committee',
store=False 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( @api.depends(
'requisition_id', 'requisition_id.state', 'requisition_id.purchase_commitee', 'requisition_id', 'requisition_id.state', 'requisition_id.purchase_commitee',
@ -168,7 +184,30 @@ class PurchaseOrder(models.Model):
'context': {'default_order_id': self.id} '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): def action_sign(self):
if self.annual_request_id and self.annual_request_id.committee_enabled: if self.annual_request_id and self.annual_request_id.committee_enabled:
@ -187,6 +226,7 @@ class PurchaseOrder(models.Model):
for order in other_orders: for order in other_orders:
order.action_unsign() order.action_unsign()
self.annual_request_id.vendor_id = self.partner_id.id self.annual_request_id.vendor_id = self.partner_id.id
self._fill_annual_prices_from_po()
if self.annual_request_id.ssd_approve: if self.annual_request_id.ssd_approve:
self.annual_request_id.state = 'ssd' self.annual_request_id.state = 'ssd'
elif self.annual_request_id.seo_approve: elif self.annual_request_id.seo_approve:

View File

@ -27,16 +27,25 @@
<button name="action_cancel" string="Cancel" type="object" states="draft,ssd,gm"/> <button name="action_cancel" string="Cancel" type="object" states="draft,ssd,gm"/>
<field name="state" widget="statusbar" statusbar_visible="draft,ssd,gm,approved,rejected,cancel"/> <field name="state" widget="statusbar" statusbar_visible="draft,ssd,gm,approved,rejected,cancel"/>
</header> </header>
<sheet> <sheet>
<div class="oe_title" name="title">
<h2>
<field name="name" readonly="1" style=" font-size:24px; "/>
</h2>
</div>
<group> <group>
<group> <group>
<field name="name" readonly="1"/> <field name="recommendation" />
<field name="agreement_id"/> <field name="vendor_id" readonly="1"/>
<field name="department_id"/> <!-- <field name="recommendation"/>-->
<field name="purpose"/> <!-- <field name="agreement_id"/>-->
<!-- <field name="department_id"/>-->
<!-- <field name="purpose"/>-->
</group> </group>
<group> <group>
<field name="vendor_id" readonly="1"/> <field name="ssd_approve" />
<field name="seo_approve" />
</group> </group>
</group> </group>
<notebook> <notebook>
@ -51,13 +60,6 @@
</tree> </tree>
</field> </field>
</page> </page>
<page string="Notes">
<field name="note"/>
</page>
<page string="Chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</page>
</notebook> </notebook>
</sheet> </sheet>
</form> </form>

View File

@ -52,6 +52,12 @@
icon="fa-file-text-o"> icon="fa-file-text-o">
<field name="attach_no" widget="statinfo" string="Documents"/> <field name="attach_no" widget="statinfo" string="Documents"/>
</button> </button>
<!-- <button class="oe_stat_button"-->
<!-- type="object"-->
<!-- name="action_open_addendums"-->
<!-- icon="fa-files-o">-->
<!-- <field name="addendum_count" widget="statinfo" string="Addendums"/>-->
<!-- </button>-->
</div> </div>
<div class="oe_title" name="title"> <div class="oe_title" name="title">
@ -104,6 +110,8 @@
<tree editable="bottom"> <tree editable="bottom">
<field name="product_id"/> <field name="product_id"/>
<field name="description"/> <field name="description"/>
<field name="price_unit" readonly="1"
attrs="{'column_invisible': [('parent.state','not in',['ssd','ceo','procurement','purchase'])]}"/>
<field name="uom_id"/> <field name="uom_id"/>
<field name="quantity"/> <field name="quantity"/>
</tree> </tree>

View File

@ -145,6 +145,9 @@
</group> </group>
</group> </group>
</page> </page>
<page string="Technical Offer Attachment">
<field name="technical_attachment_id"/>
</page>
</notebook> </notebook>
</sheet> </sheet>
</form> </form>
@ -324,11 +327,6 @@
</page> </page>
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
</form> </form>
</xpath> </xpath>
@ -337,4 +335,40 @@
<!-- Inherit the Purchase Order form -->
<record id="purchase_order_custom_form_view_Inherit" model="ir.ui.view">
<field name="name">purchase.order.form.custom</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase_requisition_custom.purchase_order_custom_form_view"/>
<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='action_sign']" position="replace">
<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> </odoo>