Metadata-Version: 2.1
Name: odoo-addon-social_media_advertising
Version: 17.0.1.0.0.2
Requires-Python: >=3.10
Requires-Dist: odoo-addon-social_media_base>=17.0dev,<17.1dev
Requires-Dist: odoo>=17.0a,<17.1dev
Summary: Campaigns, campaign groups, ads, stages and tags for social media
Home-page: https://github.com/OCA/social
License: AGPL-3
Author: Binhex, Odoo Community Association (OCA)
Author-email: support@odoo-community.org
Classifier: Programming Language :: Python
Classifier: Framework :: Odoo
Classifier: Framework :: Odoo :: 17.0
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
Classifier: Development Status :: 4 - Beta
Description-Content-Type: text/x-rst

.. image:: https://odoo-community.org/readme-banner-image
   :target: https://odoo-community.org/get-involved?utm_source=readme
   :alt: Odoo Community Association

========================
Social Media Advertising
========================

.. 
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
   !! This file is generated by oca-gen-addon-readme !!
   !! changes will be overwritten.                   !!
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
   !! source digest: sha256:050fd6e6c47701ffdc97f8af973a844cae238c8f4a7fb834ebcadda0a3ed3b5c
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
    :target: https://odoo-community.org/page/development-status
    :alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
    :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
    :alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github
    :target: https://github.com/OCA/social/tree/17.0/social_media_advertising
    :alt: OCA/social
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
    :target: https://translation.odoo-community.org/projects/social-17-0/social-17-0-social_media_advertising
    :alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
    :target: https://runboat.odoo-community.org/builds?repo=OCA/social&target_branch=17.0
    :alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module adds the advertising layer on top of *Social Media Base*:
the campaigns used to promote the posts, the groups that manage several
campaigns as a unit, the tags that classify them and the view of the ads
served by the social media.

Everything is generic, so it works with any social media. Each connector
module plugs its own social media through the extension hooks, and
contributes its own submenu under *Advertising*: campaign groups,
campaigns and ads belong to a single social media, so they are never
listed mixed together.

Main features:

- Campaign groups and campaigns, linked to a social media, to its
  accounts and to the posts they promote.
- A second campaign field on a post, next to the Odoo marketing campaign
  (``utm.campaign``) that *Social Media Base* provides: the campaign of
  the social media itself, which holds the budget and the remote
  reference. The two are independent.
- Tags to classify the campaigns.
- Stages declared per social media, so every social media keeps its own
  status vocabulary for campaigns, campaign groups and ads instead of a
  hardcoded list.
- Ads view listing the sponsored creatives of the connected accounts
  with their status, their campaign and the post they promote.
- An extensible hook so each social media module can import its
  campaigns.

**Table of contents**

.. contents::
   :local:

Configuration
=============

Security groups.
----------------

- The module reuses the groups of *Social Media Base*.
- *Social Media / User: Own Accounts* sees only the campaigns he is
  responsible for, and can create campaign groups and tags but not
  delete them.
- *Social Media / Administrator* sees every campaign and is the only
  group that manages the stages.

Advertising environment.
------------------------

- Go to *Social Media* > Configuration > Accounts, open an account and
  its *Advertising* tab.
- *Environment*, in the *Advertising* tab, tells the advertising API of
  the social media which entities it must answer for this account.

  - *Test* only sees the test advertising accounts of the application,
    their campaigns and their ads. Nothing is ever served to a real
    audience and nothing is ever billed, which is why it is the default.
  - *Production* sees the real advertising accounts. A campaign created
    from Odoo in this environment spends a real budget once it is
    activated in the campaign manager of the social media.

- **The environment does not affect the publication of posts.** A post
  published from an account set to *Test* is a real post on the social
  media, visible to everybody. The setting only governs the advertising
  features.
- The two environments never share an advertising account, so changing
  the environment drops the advertising account in use.
- The tab only shows up when an advertising connector module is
  installed for the social media of the account.

Stages.
-------

- Go to *Social Media* > Configuration > Stages > ``<social media>``, a
  menu every connector module contributes for its own social media.
- A stage mirrors a status the social media gives to a campaign, a
  campaign group or an ad. It belongs to one social media and one scope,
  set in the *Applies To* field.
- *Code* is the value the social media returns, for example ``ACTIVE``
  or ``PENDING_DELETION``. It is what the connector module matches when
  it imports, so it must be written exactly as the social media sends
  it.
- *Level* is the colour of the badge showing the stage.
- This module ships **no** stage: each connector module declares the
  stages of its own social media in its ``data/`` folder. Until a
  connector is installed, the *Stage* field has no option to choose
  from.

Connector modules.
------------------

- To resolve a remote status, call
  ``self.env["social.stage"]._get_stage(media_type, applies_to, code)``.
  It returns the stage, or an empty recordset when the connector has not
  declared that code.
- To make a social media selectable on the campaigns, extend
  ``social.advertising.campaign._available_campaign()`` and append your
  own media type.
- To restrict the social media campaigns that can be linked to a post,
  extend ``social.post._get_allow_social_campaign_domain()``.
- To freeze another field of the post once it is published, extend
  ``social.post._get_locked_content_fields()``, as this module does with
  the social media campaign.
- To list the advertising accounts of a social media, extend
  ``social.account._advertising_media_types()`` with your own media
  type, which is what makes the *Advertising* tab show up, and implement
  ``social.account._fetch_advertising_accounts()`` returning one dict of
  values per advertising account, with ``remote_ref``, ``name`` and
  ``environment``. The generic module takes care of creating, updating
  and dropping the ``social.advertising.account`` records, and of
  keeping the one in use.
- To add what only your social media reports, inherit
  ``social.advertising.account`` and declare your own fields, as *Social
  Media Advertising Linkedin* does with the serving status.
- To show your records, contribute a menu named after your social media
  under ``social_media_advertising.social_advertising_menu``, with one
  ``ir.actions.act_window`` per model filtered on
  ``[("media_id.media_type", "=", "<your media type>")]``, and another
  one under ``social_media_advertising.social_stage_root_menu`` for your
  stages. Both menus of this module are only containers: they hold
  nothing until a connector fills them, and Odoo hides them meanwhile.
- To import the campaigns and the campaign groups of a social media,
  implement ``social.account.action_import_campaigns()`` and return
  ``success``, ``message`` and the number of imported ``groups``,
  ``campaigns`` and ``ads``. It is what the *Fetch campaigns* button of
  the account calls.
- To list the ads, implement ``social.account._fetch_ads()``, returning
  one dict of values per ad, each carrying its ``remote_ref``, and
  ``social.account._fetch_ad_refs()``, returning only the references:
  the first is what *Sync ads* mirrors, the second is the cheap call the
  six-hour cron checks with.
- To delete an ad on the social media, implement
  ``social.advertising.ad._delete_remote_ad()`` and answer
  ``_compute_can_delete_remote_ad()`` for the ads your API accepts a
  deletion on. Extend ``_register_remote_ad_gone()`` to leave on the
  archived ad the status your social media gives a deleted one, and
  ``_advertising_ad_action()`` to send the user back to the list of your
  own social media.
- To give a record its address on the social media, implement
  ``_get_web_url()`` on ``social.advertising.account``,
  ``social.advertising.campaign``, ``social.advertising.campaign.group``
  or ``social.advertising.ad``: the *Open ...* button of the form shows
  up as soon as it answers one.

Usage
=====

Choose the advertising account.
-------------------------------

- Go to *Social Media* > Configuration > Accounts, open an account and
  its *Advertising* tab.
- Click *Fetch advertising accounts*. The advertising accounts the
  authorized member may reach are brought from the social media and
  listed read-only: they mirror the social media and are never pushed
  back.
- Only the advertising accounts of the current environment can be used,
  so check the *Environment* first.
- Click *Use this account* on the row you want the campaigns and the ads
  to belong to. It is highlighted and marked *In Use*, and any other one
  is unmarked: **one advertising account at a time per social media
  account**.
- **Nothing works until one is marked.** When the environment leaves
  exactly one candidate, it is marked *In Use* automatically, both when
  the advertising accounts are fetched and when the environment changes.
  With several candidates none is chosen and you have to pick one:
  campaigns, campaign groups and ads all belong to a single advertising
  account on the social media, so guessing one would silently work
  against the wrong advertiser. A choice already made is never replaced.
  While none is in use, importing and creating campaigns is refused with
  a message saying so, and no ad is fetched.
- Fetching again refreshes the list without changing your choice. An
  advertising account that no longer exists on the social media is
  dropped from the list, unless the social media returns no account at
  all: in that case nothing is removed, because an empty answer cannot
  be told apart from a temporary failure.
- Go to *Social Media* > Advertising > ``<social media>`` > Advertising
  accounts for the list of the advertising accounts of that social
  media, with filters by environment and by the one in use. Open one to
  see its details, its *Campaigns* and *Campaign Groups* stat buttons,
  and the *Open advertising account* button, which opens it on the
  social media in a new tab.
- A campaign and a campaign group record the advertising account they
  belong to when they are created on the social media or imported from
  it, and it never changes afterwards, except when the advertising
  account itself disappears from the social media on a *Fetch
  advertising accounts*: it is then dropped and the campaigns and the
  campaign groups are left without a link. Choosing another advertising
  account therefore does not move the campaigns already created: the
  stat buttons keep showing the history as it happened. A campaign or a
  campaign group holding no advertising account is linked on the next
  *Fetch campaigns*.

Stat buttons of the account.
----------------------------

- Go to *Social Media* > Configuration > Accounts and open an account.
- *Campaigns* and *Campaign Groups* cover every advertising account of
  the social media account, not only the one in use.
- *Ads* opens the ads of that account with the standard search bar, so
  they can still be filtered by date, grouped and saved as a favourite.
- All three are only shown for a social media whose connector module
  manages advertising. The *Marketing Campaigns* button next to them
  comes from *Social Media Base* and is always shown: a marketing
  campaign needs no advertising connector.

Generate a campaign group.
--------------------------

- Go to *Social Media* > Advertising > ``<social media>`` > Campaign
  Groups > New
- A form view opens; fill in the required fields |CREATE_GROUP_CAMPAIGN|
- Save
- The *Campaigns* stat button on the form shows the number of campaigns
  of the group and navigates to them.
- The *Open campaign group* button opens the campaign group on the
  social media, in a new tab. It only shows up once the campaign group
  exists there.

Generate a campaign.
--------------------

- Go to *Social Media* > Advertising > ``<social media>`` > Campaigns >
  New
- Fill in the fields. The social media is optional; when a social media
  is selected, the campaign group and the accounts become required.
  |CREATE_CAMPAIGN|
- Save
- Changes on the main campaign fields are logged in the chatter.
- A campaign can target several accounts of the same social media. A
  connector module may restrict it: LinkedIn campaigns accept a single
  account, because the campaign belongs to one advertising account
  there.
- The *Open campaign* button opens the campaign on the social media, in
  a new tab. It only shows up once the campaign exists there.

Campaign and campaign group stages.
-----------------------------------

- The status bar of the campaign form and the *Stage* field of the
  campaign group show the status the social media gives to the record.
- Only the stages of the social media selected on the campaign are
  offered, and only those whose *Applies To* matches the record.
- The stages are declared by each connector module, see the
  configuration section.

Link the campaigns to a post.
-----------------------------

A post carries two independent campaigns, and both are optional:

- *Campaign* is the Odoo marketing campaign (``utm.campaign``), the same
  one used by the mailings, the leads and the UTM tracking. Any of them
  can be selected.
- *Social campaign* is the campaign of the social media, the one holding
  the budget, the campaign group and the reference used to publish a
  sponsored post. Only social campaigns whose social media matches the
  accounts selected on the post can be chosen. A connector module
  narrows the list further with the rules of its social media, so what
  is offered are the campaigns that can actually sponsor **this** post:
  see the LinkedIn ad formats in *Social Media Advertising LinkedIn*.
- Editing the content of the post can invalidate the social campaign
  already chosen. When that happens the field is cleared as soon as the
  content changes, instead of letting the post fail when it is
  published. Pick another campaign among the ones then offered.

Both are propagated to the publications of the post. A publication
imported from the social media keeps whatever its connector was able to
resolve.

Both are frozen, like the rest of the content of the post, as soon as
one of its accounts publishes: they decide how the post goes out, so
changing them afterwards would no longer describe what is online.

|PUBLICATION_CAMPAIGNS|

Ads.
----

- Go to *Social Media* > Advertising > ``<social media>`` > Ads

- The view is empty until the first synchronization. The *Sync ads*
  button brings the sponsored creatives of every account the user is
  responsible for, and it is the only thing that does: the view shows
  the picture of the last synchronization, not what the social media
  serves right now. Each ad carries the moment it was last fetched.

- An ad is named after the text of the post it promotes, which is what
  it is recognized by. An ad promoting a post this database does not
  know is named *Post not available*.

- Every ad shows its status, its campaign, the publication it promotes
  and its statistics. The cards carry no image, so they all keep the
  same shape whether the promoted post has one or not: the images are
  shown on the publication, which the *Publication* button opens. The
  *Open ad* button opens the ad itself on the social media, not the
  campaign it belongs to. The status is the one set by the advertiser on
  the social media, with the name and the colour of the matching stage;
  the reason why the social media is serving the ad or not is only shown
  on the form of the ad.

- The statistics cover the window shown next to them. They are not the
  figures of the whole life of the ad.

- The search bar is the standard one: filter by creation date, group by
  account, advertising account, campaign or status, and save a
  favourite. The panel on the side filters by status and by account.

- Only the ads of the advertising account in use are fetched. The ones
  of the other advertising accounts stay as they were last synchronized
  and are marked *Advertising account not in use*; the *Advertising
  account in use* filter leaves them out.

- Deleting an account permanently takes with it what only that account
  could reach: its advertising accounts, its ads, and the campaigns and
  campaign groups that exist on the social media. What was written here
  and never published is kept, and only loses the account that is going
  away; so is a campaign shared with an account that stays, and a group
  that still holds a campaign of somebody else.

- An ad the social media stops answering is archived, never deleted: its
  statistics are the only trace left of what it did, and the connector
  leaves on it the status its social media gives a deleted ad. The
  *Delete permanently* button, reserved to the administrators and to the
  archived ads, is what removes that history from Odoo when it is not
  wanted; the confirmation says what is lost. Nothing is deleted on the
  social media, which no longer serves that ad anyway.

- The *Delete ad* button deletes the ad **on the social media**, not
  only in Odoo, and it cannot be undone. It is only shown for the social
  media whose connector deletes an ad, and only the responsible user of
  the account and the social media administrators may press it. What the
  social media answers decides what is left in Odoo: an ad the social
  media deletes on the spot loses its record here as well, along with
  its statistics, while one that is only taken for deletion keeps its
  record with the status the social media reports until it is processed.

- An ad the social media stops serving is archived, never deleted, so
  its statistics survive. Archiving an account archives its ads too;
  deleting it deletes them.

- A cron checks every six hours whether the social media serves ads this
  database does not know yet. No ad is fetched: it only raises a flag on
  the account. The responsible user gets a notice on the ads view, and
  the *New ads available* badge stays there across reloads until the ads
  are synchronized, so the user decides when to press *Sync ads*.

  |ADS|

Fetch campaigns.
----------------

- The account form provides a *Fetch campaigns* button when the
  connector module adds it. Each social media module implements the
  actual import; a notification shows the result.
- The import can also link the publications already brought from the
  social media with the campaign they belong to, when the connector is
  able to resolve it.

Archiving.
----------

- Archiving an account also archives its campaigns and their campaign
  groups.
- A campaign shared by several accounts is only archived when all of its
  accounts are, whether they are archived together or one by one, and a
  campaign group is only archived when it has no active campaign left.
- Unarchiving the account restores its campaigns and their campaign
  groups. The ads are not brought back: an ad is also archived when the
  social media stops serving it, so unarchiving them all would resurrect
  the ones it dropped, and the next synchronization is what restores the
  ones still served.
- Deleting an account permanently deletes, in Odoo, the campaigns and
  the campaign groups that exist on the social media; nothing is deleted
  on the social media itself. A campaign that was only written in Odoo
  and never reached the social media is kept, and it only loses the link
  to the account; a campaign group is kept as long as it still has a
  campaign.

Campaign ownership.
-------------------

- Every campaign has a *Responsible* user, set to whoever created it. A
  regular user of the *Social Media / User: Own Accounts* group only
  sees the campaigns he is responsible for; the *Social Media /
  Administrator* group sees all of them.

Notifications you may never see.
--------------------------------

- The check for new ads runs every six hours and raises a notice on the
  ads view when the social media serves an ad this database does not
  know about. It needs an advertising account marked as *in use* on the
  social media account: without one there is no advertiser to ask, the
  check reads nothing and the notice never goes up.

.. |CREATE_GROUP_CAMPAIGN| image:: https://raw.githubusercontent.com/OCA/social/17.0/social_media_advertising/static/img/readme/CREATE_GROUP_CAMPAIGN.png
.. |CREATE_CAMPAIGN| image:: https://raw.githubusercontent.com/OCA/social/17.0/social_media_advertising/static/img/readme/CREATE_CAMPAIGN.png
.. |PUBLICATION_CAMPAIGNS| image:: https://raw.githubusercontent.com/OCA/social/17.0/social_media_advertising/static/img/readme/PUBLICATION_CAMPAIGNS.png
.. |ADS| image:: https://raw.githubusercontent.com/OCA/social/17.0/social_media_advertising/static/img/readme/ADS.png

Known issues / Roadmap
======================

Kanban view of the campaigns.
-----------------------------

The ``fold`` field of ``social.stage`` is meant for a kanban of the
campaigns grouped by stage, which is not implemented yet. Until then the
field has no visible effect.

Tags without colour.
--------------------

``social.tag`` only has a name: it has neither a colour field for the
``many2many_tags`` widget nor a uniqueness constraint on the name, so
two tags can share it.

Campaign groups and tags are shared data.
-----------------------------------------

``social.advertising.campaign.group`` and ``social.tag`` have no
responsible and no record rule, so every social media user sees and
edits all of them, unlike the advertising accounts, the campaigns and
the ads, which are restricted to their responsible. Scoping them is left
for a later version.

Campaign visibility of a shared post.
-------------------------------------

The record rule of ``social.advertising.campaign`` restricts a user to
the campaigns he is responsible for. If an administrator assigns a post
to a user and that post is linked to a campaign of somebody else, that
user cannot open the campaign from the post, and the *Social Campaign*
field offers him nothing to choose from. A finer rule, for instance
sharing the campaigns of a campaign group, is left for a later version.

Statistics of a single window.
------------------------------

There is no history per day, so the evolution of an ad cannot be
charted: a new synchronization replaces the statistics of the previous
one. Charting it needs a table of facts per day, the way
``social_media_base`` already keeps one for the accounts in
``social.account.statistics``, and giving the advertising models the
same treatment is a feature of its own.

``remote_ref`` is writable through RPC.
---------------------------------------

The views hide ``remote_ref``, and on the ads and the advertising
accounts the ACL backs it: a social media user only reads them. The
campaigns and the campaign groups are the ones he writes, so on those
two he can still change or empty the field through RPC and orphan the
record on the social media. Restricting the field is a hardening
candidate for a later version.

Connector modules without an advertising layer.
-----------------------------------------------

Only LinkedIn has its advertising connector, *Social Media Advertising
LinkedIn*. The other connectors of the family, such as
``social_media_x``, have no advertising module yet: their accounts
publish posts but do not manage campaigns.

Records without a social media are out of every menu.
-----------------------------------------------------

The lists of campaign groups, campaigns and ads are filtered on the
social media they belong to. A campaign saved without one, or a campaign
group whose campaigns mix social medias, which empties its media, is
therefore reachable only through a stat button or a saved filter.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/social/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/social/issues/new?body=module:%20social_media_advertising%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Binhex

Contributors
------------

- `Binhex <https://www.binhex.cloud>`__:

  - Edilio Escalona Almira e.escalona@binhex.cloud
  - Jorge Elena Poblet j.elena@binhex.cloud

Other credits
-------------

This work has been funded by AEOdoo (Asociación Española de Odoo -
https://www.aeodoo.org)

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
   :alt: Odoo Community Association
   :target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

.. |maintainer-edescalona| image:: https://github.com/edescalona.png?size=40px
    :target: https://github.com/edescalona
    :alt: edescalona

Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-edescalona| 

This module is part of the `OCA/social <https://github.com/OCA/social/tree/17.0/social_media_advertising>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
