community_classifieds: a member-gated classifieds board. community.classified (title/category/description/up-to-3-images via a child .image model/contact info/state) with a public board at /classifieds, detail pages that track view_count, portal posting at /classifieds/new (soft-detects community_membership - blocks non-active-members only if that module is installed, otherwise anyone logged in can post), a "my listings" portal page with self-service renew, and an admin moderation queue (publish/reject). Daily crons expire past-due listings and send a 7-day expiry warning; a new-submission notice goes out to the Classifieds Moderator group. Listing duration is configurable (Settings), never hardcoded. community_benefits: community.benefit.partner (benefit centres) offer community.benefit entitlements scoped to specific community_membership tiers via tier_ids. community.benefit.redemption logs a redemption but its create() is guarded by a constraint that re-checks the same active-membership condition the membership QR verification page uses (membership_state in active/renewal_due) plus tier entitlement, refusing the redemption otherwise. Public directory at /benefits, portal page at /my/benefits listing only benefits the member's tier actually entitles them to. Two more real Odoo 19 API changes hit here: search-view <group> elements for "Group By" sections no longer accept a `string` attribute (must use `name` only - same fix as community_membership's partner search view, applied here to a fresh module), and res.groups.users was renamed to user_ids. Verified against a live Odoo 19 + Postgres 16 container: 7+4 automated tests pass, plus a full manual live run covering the Phase 4 gate exactly - confirmed a non-member is blocked from /classifieds/new, activated a real membership, posted a classified (pending_review), published it via the moderator action, confirmed it appears on the public board and detail page with view_count incrementing, entitled a tier to a benefit, logged a redemption for the active member, confirmed it shows on /my/benefits, and confirmed redemption creation is refused for a non-member. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
86 lines
3.9 KiB
XML
86 lines
3.9 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<odoo>
|
|
<record id="view_classified_list" model="ir.ui.view">
|
|
<field name="name">community.classified.list</field>
|
|
<field name="model">community.classified</field>
|
|
<field name="arch" type="xml">
|
|
<list string="Classifieds">
|
|
<field name="title"/>
|
|
<field name="category"/>
|
|
<field name="poster_partner_id"/>
|
|
<field name="post_date"/>
|
|
<field name="expiry_date"/>
|
|
<field name="state" decoration-warning="state == 'pending_review'"/>
|
|
</list>
|
|
</field>
|
|
</record>
|
|
|
|
<record id="view_classified_form" model="ir.ui.view">
|
|
<field name="name">community.classified.form</field>
|
|
<field name="model">community.classified</field>
|
|
<field name="arch" type="xml">
|
|
<form string="Classified">
|
|
<header>
|
|
<button name="action_publish" type="object" string="Publish" class="btn-primary"
|
|
invisible="state == 'published'"/>
|
|
<button name="action_reject" type="object" string="Reject"
|
|
invisible="state == 'rejected'"/>
|
|
<field name="state" widget="statusbar"/>
|
|
</header>
|
|
<sheet>
|
|
<div class="oe_title"><h1><field name="title"/></h1></div>
|
|
<group>
|
|
<group>
|
|
<field name="category"/>
|
|
<field name="poster_partner_id"/>
|
|
<field name="contact_method"/>
|
|
<field name="contact_email"/>
|
|
<field name="contact_phone"/>
|
|
</group>
|
|
<group>
|
|
<field name="post_date"/>
|
|
<field name="expiry_date"/>
|
|
<field name="view_count"/>
|
|
</group>
|
|
</group>
|
|
<group string="Description">
|
|
<field name="description" nolabel="1"/>
|
|
</group>
|
|
<group string="Moderation Notes">
|
|
<field name="admin_notes" nolabel="1"/>
|
|
</group>
|
|
</sheet>
|
|
</form>
|
|
</field>
|
|
</record>
|
|
|
|
<record id="view_classified_search" model="ir.ui.view">
|
|
<field name="name">community.classified.search</field>
|
|
<field name="model">community.classified</field>
|
|
<field name="arch" type="xml">
|
|
<search string="Classifieds">
|
|
<field name="title"/>
|
|
<filter string="Pending Review" name="pending_review" domain="[('state', '=', 'pending_review')]"/>
|
|
<filter string="Published" name="published" domain="[('state', '=', 'published')]"/>
|
|
<group name="group_by">
|
|
<filter string="Category" name="group_by_category" context="{'group_by': 'category'}"/>
|
|
<filter string="Status" name="group_by_state" context="{'group_by': 'state'}"/>
|
|
</group>
|
|
</search>
|
|
</field>
|
|
</record>
|
|
|
|
<record id="action_classified_moderation" model="ir.actions.act_window">
|
|
<field name="name">Classifieds Moderation</field>
|
|
<field name="res_model">community.classified</field>
|
|
<field name="view_mode">list,form</field>
|
|
<field name="search_view_id" ref="view_classified_search"/>
|
|
<field name="context">{'search_default_pending_review': 1}</field>
|
|
</record>
|
|
|
|
<menuitem id="menu_classifieds_root" name="Classifieds" sequence="27"
|
|
groups="community_classifieds.group_classifieds_moderator"/>
|
|
<menuitem id="menu_classifieds_moderation" name="Moderation"
|
|
parent="menu_classifieds_root" action="action_classified_moderation" sequence="10"/>
|
|
</odoo>
|