no payment
This commit is contained in:
greg
2025-11-30 19:31:24 -06:00
commit 44be2f5cf2
16 changed files with 589 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/warranty_claim.iml" filepath="$PROJECT_DIR$/.idea/warranty_claim.iml" />
</modules>
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="FacetManager">
<facet type="Python" name="Python facet">
<configuration sdkName="Python 3.14 (warranty_claim)" />
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/.venv" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Python 3.14 (warranty_claim) interpreter library" level="application" />
</component>
</module>
+1
View File
@@ -0,0 +1 @@
from . import models
+25
View File
@@ -0,0 +1,25 @@
{
"name": "Warranty Claims",
"version": "17.0.1.0.0",
"summary": "Manage product warranty claims and vendor RMA",
"category": "Sales",
"author": "Wisc-Host",
"depends": [
"base",
"mail",
"sale_management",
"account",
"purchase",
],
"data": [
"security/ir.model.access.csv",
"views/sale_order_warranty_views.xml",
"views/warranty_claim_views.xml",
"views/warranty_menus.xml",
"views/warranty_vendor_views.xml",
"report/report_warranty_claim.xml",
# "data/mail_template_warranty_claim.xml",
],
"application": True,
"installable": True,
}
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<!-- Intentionally empty for now -->
</data>
</odoo>
+3
View File
@@ -0,0 +1,3 @@
from . import sale_order_warranty
from . import warranty_claim
from . import warranty_vendor
+116
View File
@@ -0,0 +1,116 @@
from odoo import api, fields, models
class SaleOrder(models.Model):
_inherit = "sale.order"
# Checkbox under customer
warranty_order = fields.Boolean(string="Warranty Order")
# Manufacturer (your logical “warranty manufacturer” entity)
warranty_manufacturer_id = fields.Many2one(
"warranty.manufacturer",
string="Manufacturer",
)
# Warranty type
warranty_type = fields.Selection(
[
("90_day", "90 Day Warranty"),
("cabinet", "Cabinet Warranty"),
("other", "Other"),
],
string="Warranty Type",
)
# “True Vendor” (actual vendor that handles warranty, may differ from manufacturer)
warranty_true_vendor_id = fields.Many2one(
"res.partner",
string="True Vendor",
domain="[('supplier_rank', '>', 0)]",
)
# Fields manufacturer requires
warranty_model = fields.Char("Model")
warranty_serial = fields.Char("Serial Number")
warranty_failure = fields.Text("Failure Description")
# Original sales order (dropdown filtered to orders with matching part numbers)
# We'll add a helper many2many for domain support.
warranty_original_sale_id = fields.Many2one(
"sale.order",
string="Original Sales Order",
domain="[('id', 'in', warranty_original_sale_ids)]",
)
warranty_original_sale_ids = fields.Many2many(
"sale.order",
compute="_compute_warranty_original_sale_ids",
string="Candidate Original Sales Orders",
store=False,
)
@api.depends("order_line.product_id")
def _compute_warranty_original_sale_ids(self):
"""Find previous sales orders for the same customer containing any
of the products on this warranty order.
"""
for order in self:
if not order.partner_id or not order.order_line:
order.warranty_original_sale_ids = False
continue
product_ids = order.order_line.product_id.ids
if not product_ids:
order.warranty_original_sale_ids = False
continue
candidates = self.search([
("id", "!=", order.id),
("partner_id", "=", order.partner_id.id),
("state", "in", ["sale", "done"]),
("order_line.product_id", "in", product_ids),
], limit=50)
order.warranty_original_sale_ids = candidates
def action_create_warranty_claim(self):
"""Button on SO warranty tab: create a warranty.claim pre-filled."""
self.ensure_one()
claim_vals = {
"sale_order_id": self.id,
"partner_id": self.partner_id.id,
"manufacturer_id": self.warranty_manufacturer_id.id,
"true_vendor_id": self.warranty_true_vendor_id.id,
"model": self.warranty_model,
"serial_number": self.warranty_serial,
"failure": self.warranty_failure,
"warranty_type": self.warranty_type,
"original_sale_order_id": self.warranty_original_sale_id.id,
}
claim = self.env["warranty.claim"].create(claim_vals)
action = self.env.ref("odoo_warranty_claims.action_warranty_claims").read()[0]
action["res_id"] = claim.id
action["views"] = [(self.env.ref("odoo_warranty_claims.view_warranty_claim_form").id, "form")]
return action
def _create_invoices(self, grouped=False, final=False, date=None):
invoices = super()._create_invoices(grouped=grouped, final=final, date=date)
for order in self:
if order.warranty_order:
# Link customer invoice(s) to the new claim
claim = self.env["warranty.claim"].create({
"sale_order_id": order.id,
"partner_id": order.partner_id.id,
"manufacturer_id": order.warranty_manufacturer_id.id,
"true_vendor_id": order.warranty_true_vendor_id.id,
"model": order.warranty_model,
"serial_number": order.warranty_serial,
"failure": order.warranty_failure,
"warranty_type": order.warranty_type,
"original_sale_order_id": order.warranty_original_sale_id.id,
"invoice_customer_ids": [(6, 0, invoices.ids)],
})
# you could also auto-open or email here, but usually this is backend logic
return invoices
+94
View File
@@ -0,0 +1,94 @@
from odoo import api, fields, models
class WarrantyClaim(models.Model):
_name = "warranty.claim"
_description = "Warranty Claim"
_inherit = ["mail.thread", "mail.activity.mixin"]
_order = "create_date desc"
name = fields.Char("Claim Reference", required=True, copy=False, default="New", tracking=True)
# Link to the customer and various parties
partner_id = fields.Many2one("res.partner", string="Customer", tracking=True)
manufacturer_id = fields.Many2one("warranty.manufacturer", string="Manufacturer")
true_vendor_id = fields.Many2one(
"res.partner",
string="True Vendor",
domain="[('supplier_rank', '>', 0)]",
tracking=True,
)
extra_recipient_ids = fields.Many2many(
"res.partner",
string="Additional Email Recipients",
help="Separate recipients you want CC'd on the warranty email.",
)
# Link to sales and invoices
sale_order_id = fields.Many2one("sale.order", string="Sales Order", tracking=True)
original_sale_order_id = fields.Many2one("sale.order", string="Original Sales Order")
# Customer invoices to attach
invoice_customer_ids = fields.Many2many(
"account.move",
"warranty_claim_customer_invoice_rel",
"claim_id", "move_id",
string="Customer Invoices",
domain="[('move_type', 'in', ('out_invoice', 'out_refund'))]",
)
# Vendor invoices to attach
invoice_vendor_ids = fields.Many2many(
"account.move",
"warranty_claim_vendor_invoice_rel",
"claim_id", "move_id",
string="Vendor Invoices",
domain="[('move_type', 'in', ('in_invoice', 'in_refund'))]",
)
# Technical / product fields
product_id = fields.Many2one("product.product", string="Product")
model = fields.Char("Model")
serial_number = fields.Char("Serial Number")
failure = fields.Text("Failure Description")
warranty_type = fields.Selection(
[
("90_day", "90 Day Warranty"),
("cabinet", "Cabinet Warranty"),
("other", "Other"),
],
string="Warranty Type",
)
claim_date = fields.Date("Claim Date", default=fields.Date.context_today)
state = fields.Selection(
[
("draft", "Draft"),
("sent", "Sent to Vendor"),
("in_progress", "In Progress"),
("approved", "Approved"),
("rejected", "Rejected"),
("done", "Done"),
],
string="Status",
default="draft",
tracking=True,
)
@api.model
def create(self, vals):
if vals.get("name", "New") == "New":
seq = self.env.ref(
"odoo_warranty_claims.seq_warranty_claim",
raise_if_not_found=False
)
if seq:
vals["name"] = seq.next_by_id()
return super().create(vals)
def action_send_to_vendor(self):
"""Uses an email template and moves to 'sent' state."""
self.ensure_one()
template = self.env.ref("odoo_warranty_claims.mail_template_warranty_claim", raise_if_not_found=False)
if template:
template.send_mail(self.id, force_send=True)
self.state = "sent"
return True
+18
View File
@@ -0,0 +1,18 @@
from odoo import fields, models
class WarrantyManufacturer(models.Model):
_name = "warranty.manufacturer"
_description = "Warranty Manufacturer"
name = fields.Char(required=True)
vendor_id = fields.Many2one(
"res.partner",
string="True Vendor",
domain="[('supplier_rank', '>', 0)]",
)
# Flags for what this manufacturer requires
requires_model = fields.Boolean(default=True)
requires_serial = fields.Boolean(default=True)
requires_failure = fields.Boolean(default=True)
requires_original_so = fields.Boolean(default=True)
notes = fields.Text("Vendor Warranty Instructions")
+53
View File
@@ -0,0 +1,53 @@
<odoo>
<report
id="action_report_warranty_claim"
model="warranty.claim"
string="Warranty Claim"
report_type="qweb-pdf"
name="odoo_warranty_claims.report_warranty_claim"
file="odoo_warranty_claims.report_warranty_claim"
/>
<template id="report_warranty_claim">
<t t-call="web.external_layout">
<t t-set="doc" t-value="doc"/>
<div class="page">
<h2>Warranty Claim <t t-esc="doc.name"/></h2>
<p>
Customer: <t t-esc="doc.partner_id.name"/><br/>
Manufacturer: <t t-esc="doc.manufacturer_id.name"/><br/>
True Vendor: <t t-esc="doc.true_vendor_id.name"/><br/>
Claim Date: <t t-esc="doc.claim_date"/>
</p>
<h3>Product / Failure</h3>
<p>
Product: <t t-esc="doc.product_id.display_name"/><br/>
Model: <t t-esc="doc.model"/><br/>
Serial: <t t-esc="doc.serial_number"/><br/>
</p>
<p>
<strong>Failure:</strong><br/>
<t t-esc="doc.failure"/>
</p>
<h3>Invoices</h3>
<p>
Customer Invoices:
<t t-foreach="doc.invoice_customer_ids" t-as="inv">
<br/><t t-esc="inv.name"/>
</t>
</p>
<p>
Vendor Invoices:
<t t-foreach="doc.invoice_vendor_ids" t-as="vinv">
<br/><t t-esc="vinv.name"/>
</t>
</p>
</div>
</t>
</template>
</odoo>
+3
View File
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_warranty_claim_user,warranty.claim.user,model_warranty_claim,base.group_user,1,1,1,1
access_warranty_manufacturer_admin,warranty.manufacturer.admin,model_warranty_manufacturer,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_warranty_claim_user warranty.claim.user model_warranty_claim base.group_user 1 1 1 1
3 access_warranty_manufacturer_admin warranty.manufacturer.admin model_warranty_manufacturer base.group_system 1 1 1 1
+49
View File
@@ -0,0 +1,49 @@
<odoo>
<record id="view_order_form_inherit_warranty" model="ir.ui.view">
<field name="name">sale.order.form.warranty</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<!-- Checkbox under customer -->
<xpath expr="//field[@name='partner_id']" position="after">
<field name="warranty_order"/>
</xpath>
<!-- Add Warranty tab (always visible for now; no attrs/states) -->
<xpath expr="//page[@name='order_lines']" position="after">
<page string="Warranty">
<group>
<group>
<field name="warranty_manufacturer_id"/>
<field name="warranty_true_vendor_id"/>
<field name="warranty_type"/>
</group>
<group>
<!-- Needed because it's used in the domain -->
<field name="warranty_original_sale_ids" invisible="1"/>
<field name="warranty_model"/>
<field name="warranty_serial"/>
<field name="warranty_original_sale_id"
domain="[('id', 'in', warranty_original_sale_ids)]"
context="{'default_partner_id': partner_id}"/>
</group>
</group>
<group>
<field name="warranty_failure" widget="text"/>
</group>
<footer>
<button name="action_create_warranty_claim"
type="object"
string="Create Warranty Claim"
class="btn-primary"/>
</footer>
</page>
</xpath>
</field>
</record>
</odoo>
+99
View File
@@ -0,0 +1,99 @@
<odoo>
<data>
<!-- Tree view -->
<record id="view_warranty_claim_tree" model="ir.ui.view">
<field name="name">warranty.claim.tree</field>
<field name="model">warranty.claim</field>
<field name="arch" type="xml">
<tree string="Warranty Claims">
<field name="name"/>
<field name="claim_date"/>
<field name="partner_id"/>
<field name="true_vendor_id"/>
<field name="manufacturer_id"/>
<field name="sale_order_id"/>
<field name="state"/>
</tree>
</field>
</record>
<!-- Form view -->
<record id="view_warranty_claim_form" model="ir.ui.view">
<field name="name">warranty.claim.form</field>
<field name="model">warranty.claim</field>
<field name="arch" type="xml">
<form string="Warranty Claim">
<sheet>
<header>
<button name="action_send_to_vendor"
type="object"
string="Send to Vendor"
class="btn-primary"/>
<field name="state" widget="statusbar"/>
</header>
<group>
<group>
<field name="name" readonly="1"/>
<field name="claim_date"/>
<field name="warranty_type"/>
</group>
<group>
<field name="partner_id"/>
<field name="sale_order_id"/>
<field name="original_sale_order_id"/>
</group>
</group>
<notebook>
<page string="Product / Failure">
<group>
<field name="product_id"/>
<field name="model"/>
<field name="serial_number"/>
</group>
<group>
<field name="failure" widget="text"/>
</group>
</page>
<page string="Vendor &amp; Manufacturer">
<group>
<field name="manufacturer_id"/>
<field name="true_vendor_id"/>
<field name="extra_recipient_ids" widget="many2many_tags"/>
</group>
</page>
<page string="Invoices">
<group string="Customer Invoices">
<field name="invoice_customer_ids" widget="many2many_tags"/>
</group>
<group string="Vendor Invoices">
<field name="invoice_vendor_ids" widget="many2many_tags"/>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="message_ids"/>
<field name="activity_ids"/>
</div>
</form>
</field>
</record>
<!-- Action -->
<record id="action_warranty_claims" model="ir.actions.act_window">
<field name="name">Warranty Claims</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">warranty.claim</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_warranty_claim_tree"/>
</record>
</data>
</odoo>
+25
View File
@@ -0,0 +1,25 @@
<odoo>
<menuitem
id="menu_warranty_root"
name="Warranty"
sequence="40"
web_icon="odoo_warranty_claims,static/description/icon.png"
/>
<menuitem
id="menu_warranty_claims_root"
name="Orders"
parent="menu_warranty_root"
sequence="10"
/>
<menuitem
id="menu_warranty_claims"
name="Warranty Claims"
parent="menu_warranty_claims_root"
action="action_warranty_claims"
sequence="10"
/>
</odoo>
+64
View File
@@ -0,0 +1,64 @@
<odoo>
<record id="view_warranty_manufacturer_tree" model="ir.ui.view">
<field name="name">warranty.manufacturer.tree</field>
<field name="model">warranty.manufacturer</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="vendor_id"/>
<field name="requires_model"/>
<field name="requires_serial"/>
<field name="requires_failure"/>
<field name="requires_original_so"/>
</tree>
</field>
</record>
<record id="view_warranty_manufacturer_form" model="ir.ui.view">
<field name="name">warranty.manufacturer.form</field>
<field name="model">warranty.manufacturer</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name"/>
<field name="vendor_id"/>
</group>
<group string="Required Fields">
<field name="requires_model"/>
<field name="requires_serial"/>
<field name="requires_failure"/>
<field name="requires_original_so"/>
</group>
<group string="Instructions">
<field name="notes"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_warranty_manufacturer" model="ir.actions.act_window">
<field name="name">Manufacturers</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">warranty.manufacturer</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem
id="menu_warranty_configuration"
name="Configuration"
parent="menu_warranty_root"
sequence="90"
/>
<menuitem
id="menu_warranty_manufacturer"
name="Manufacturers"
parent="menu_warranty_configuration"
action="action_warranty_manufacturer"
sequence="10"
/>
</odoo>