revbank/lib/RevBank/Products.pod
2024-12-26 03:34:34 +01:00

185 lines
7.3 KiB
Text

=head1 NAME
RevBank::Products - Product list
=head1 SYNOPISIS
# Comments are lines that begin with a # character.
# Empty lines are ignored.
8710447032756 0.80 "Festini Peer"
4029764001807,clubmate 1.40 "Club-Mate" +pf +half
pf 0.15@+pfand "Pfand NRW-Flasche" #OPAQUE
+half -50% "50% discount \\o/"
123 0.42 "Hashtag example" #tag #tag2=42
=head1 DESCRIPTION
This module implements a products database, based on a text file. It supports
additional fees, discounts, compound products, and optional metadata that can
be read by plugins.
=head2 read_products
The only function of this module is exported by default. It returns a reference
to a hash of products (each represented as a hash), keyed by product id.
The available keys per product are currently not documented; refer to the
C<Products.pm> file after the line that is commented C<# HERE> for a list.
=head1 CONFIGURATION
The configuration for this plugin lives in a text file called
C<revbank.products>.
Whitespace at the beginning or end of a line are ignored. Blank lines are
ignored. Comments are lines that start with C<#> and are also ignored. Note
that a whole line is either a comment or a data line; trailing comments are
not supported and C<#> is a valid character in a product description.
Data lines have whitespace-separated columns:
=head2 Product ids
One or more product ids, separated by commas (no whitespace before or after the
commas). There is no way to have a comma or whitespace in a product id, but
every other printable character is valid.
The first product id on the line is considered canonical, the rest are aliases.
Note: if a product id is the same as another RevBank command (e.g. a username),
the first plugin that accepts the command will "win"; the precedence order is
defined by the C<revbank.plugins> configuration file. However, when a product
id appears multiple times within C<revbank.products>, the I<last> one is used.
Product ids that begin with C<+> can only be used as addons. When entered as
user input, it will be ignored by the C<products> plugin.
=head2 Price
The price of the product. This is the price to be deducted from the user's
account when they check out with this product in the cart. When it is a
negative number, the user will instead have money added to their account when
"buying" this product.
Optionally, the price can be augmented with an C<@> sign and the name of the
contra account. When no contra account is specified, C<+sales/products> is used.
Internal accounts (that start with C<-> or C<+>) are created automatically. A
regular account can also be used, but has to exist before the product can be
used.
(Note on internal accounts because they aren't documented elsewhere: liability
and revenue accounts begin with C<+>, asset and expense accounts begin with
C<->. The C<+> accounts typically grow larger over time, while C<-> accounts
typically go negative. In general, you would use a C<+> account in
C<revbank.products>. User accounts are liability accounts.)
=head2 Description
The description, like other columns, may contain whitespace, but to use
whitespace, either the entire field "needs quotes" around it, or the whitespace
can be escaped with backslashes.
It is suggested to always use quotes around the description.
=head2 Additional fields
=head3 Addons
Addons are products that are added as part of the main product. They are
specified after the description, with a C<+> sign that has whitespace before
it, and no whitespace after it.
When specifying an addon C<+foo>, and no product with the id C<+foo> exists,
the product id C<foo> is used instead. The difference is that a product id
C<+foo> can only be used as an addon for another product, while C<foo> can be
used either as an addon or a manually entered as a standalone product.
example_id 2.20 "Example product" +first +second
+first 1.20 "First thing"
second 0.80 "Second thing"
In this example, the final price of the example product will be 4.20. It is not
possible to buy the first thing separate, but it is possible to buy the second
thing separate.
The addon product must be specified in C<revbank.products>; market products
cannot be used as addons.
When a product has addons, it becomes a compound product. This can be used to
separate a product into individual counter accounts for bookkeeping purposes,
to add a bottle deposit, or to add other additional fees or discounts.
When a compound product has a bare price that isn't 0.00, the bare price is
listed as a component named "Product".
A product can have multiple addons. Addon products themselves can also have
further addons, but circular recursion is not supported.
=head4 Percentage addons
As a special case, an addon's price can be a percentage. In this case, the
price is calculated from the sum of the the product components I<up to that
point> that have I<the same contra account> as the percentage addon.
So, given the following example,
example_id 0.90 "Example product" +some_fee +discount
+some_fee 0.15@+fees "Some fee; might be a bottle deposit"
+discount -50% "Special offer discount!"
only 0.45 is discounted, because the 0.15 has a different contra account. While
complicated, this is probably what you want in most cases. There is currently
no way to apply a discount to the product with all of its addons.
A percentage addon must have a product_id that begins with C<+>.
=head3 Tags
Additional metadata can be given in additional fields that begin with C<#> and
the name of the tag, optionally followed by C<=> and a value to turn it into a
key/value pair. If no value is specified, a value of C<1> is used.
The name of a hashtag must contain only C<A-Z a-z 0-9 _> characters. There must
not be whitespace after the C<#> or around the C<=>.
Like all the fields, the field can be quoted to contain whitespace. Note,
however, that the quotes must be placed around the entire field, not just the
value part.
ht1 0.42 "Just one hashtag" #tag
ht2 0.42 "Two hashtags!" #tag #key=value
ht3 0.42 "Surprising syntax" "#x=spaces in value"
Tags can be accessed by custom plugins.
The following tags are used by RevBank itself:
=over 10
=item C<#OPAQUE>
When used on an addon, the price of the addon will be excluded when calculating
the tag price. The default is to use transparent pricing, i.e. that all
additional fees are included in the tag price. In specific cases, such as
container deposits, the addon price should not be considered part of the
product price, and C<#OPAQUE> can be used.
The tag price is not displayed in the RevBank user interface, but may be used
in generated price tags and price listings.
The sum of a product's opaque prices is available via the key C<hidden_fees>.
=back
By convention, tags that affect internal semantics get uppercase names. It is
suggested that tags used only by plugins get C<lowercase> names.
=head3 Other additional fields
When any field is added after the description, that does not begin with C<+> or
C<#>, RevBank currently assumes it's the old syntax (which is not described in
the current version of this document!), and parses it using the old semantics
while showing a warning.
This compatibility feature will be removed from a future version of RevBank.